diff --git a/.appveyor.yml b/.appveyor.yml index da2aec993979..13705adc99f9 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -17,7 +17,7 @@ skip_commits: clone_depth: 50 -image: Visual Studio 2019 +image: Visual Studio 2022 environment: @@ -45,9 +45,8 @@ cache: init: - ps: - # Pinned due to https://github.com/mamba-org/mamba/issues/3467 Invoke-Webrequest - -URI https://github.com/mamba-org/micromamba-releases/releases/download/1.5.10-0/micromamba-win-64.tar.bz2 + -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\ @@ -55,6 +54,7 @@ init: - micromamba shell init --shell cmd.exe - micromamba config set always_yes true - micromamba config prepend channels conda-forge + - micromamba info install: - micromamba env create -f environment.yml python=%PYTHON_VERSION% pywin32 @@ -63,7 +63,7 @@ install: test_script: # Now build the thing.. - set LINK=/LIBPATH:%cd%\lib - - pip install -v --no-build-isolation --config-settings=setup-args="--vsenv" --editable .[dev] + - 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' @@ -91,10 +91,10 @@ artifacts: on_finish: - micromamba install codecov - - codecov -e PYTHON_VERSION PLATFORM -n "$PYTHON_VERSION Windows" + - codecov -e PYTHON_VERSION PLATFORM -n "%PYTHON_VERSION% Windows" on_failure: - # Generate a html for visual tests + # Generate an html for visual tests - python tools/visualize_tests.py --no-browser - echo zipping images after a failure... - 7z a result_images.zip result_images\ | grep -v "Compressing" diff --git a/.circleci/config.yml b/.circleci/config.yml index e7348b868d4b..40ba933cf0d9 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -98,7 +98,7 @@ commands: parameters: numpy_version: type: string - default: "~=2.0.0" + default: "" steps: - run: name: Install Python dependencies diff --git a/.flake8 b/.flake8 deleted file mode 100644 index 7297d72b5841..000000000000 --- a/.flake8 +++ /dev/null @@ -1,91 +0,0 @@ -[flake8] -max-line-length = 88 -select = - # flake8 default - D, E, F, W, -ignore = - # flake8 default - E121,E123,E126,E226,E24,E704,W503,W504, - # Additional ignores: - E127, E131, - E266, - E305, E306, - E741, - F841, - # pydocstyle - D100, D101, D102, D103, D104, D105, D106, - D200, D202, D204, D205, - D301, - D400, D401, D403, D404 - # ignored by pydocstyle numpy docstring convention - D107, D203, D212, D402, D413, D415, D416, D417, - # while D213 is ignored by the numpy docstring convention, it's left out above, - # because we want to enforce it. - -exclude = - .git - build - doc/gallery - doc/tutorials - # External files. - tools/gh_api.py - .tox - .eggs - -per-file-ignores = - 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/mathtext.py: E221 - lib/matplotlib/pylab.py: F401, F403 - lib/matplotlib/pyplot.py: F811 - lib/matplotlib/tests/test_mathtext.py: E501 - lib/matplotlib/transforms.py: E201, E202 - 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 - - doc/conf.py: E402 - galleries/users_explain/quick_start.py: E402 - galleries/users_explain/artists/paths.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 - - 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 -force-check = True diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 045386dc7402..f3595d2b7865 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -15,7 +15,9 @@ body: attributes: label: Code for reproduction description: >- - If possible, please provide a minimum self-contained example. + 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: @@ -80,6 +82,8 @@ body: options: - pip - conda + - pixi + - uv - Linux package manager - from source (.tar.gz) - git checkout diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 7f17a80dc4b6..06cd0d2fd3bf 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -4,15 +4,24 @@ out the development guide https://matplotlib.org/devdocs/devel/index.html --> ## PR summary - +## AI Disclosure + ## PR checklist diff --git a/.github/labeler.yml b/.github/labeler.yml index 75adfed57f43..77b79146b47f 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -1,7 +1,9 @@ --- "CI: Run cibuildwheel": - changed-files: - - any-glob-to-any-file: ['.github/workflows/cibuildwheel.yml'] + - any-glob-to-any-file: + - '.github/workflows/cibuildwheel.yml' + - '.github/workflows/wasm.yml' "CI: Run cygwin": - changed-files: - any-glob-to-any-file: ['.github/workflows/cygwin.yml'] diff --git a/.github/workflows/autoclose_comment.yml b/.github/workflows/autoclose_comment.yml new file mode 100644 index 000000000000..4d18d82a801f --- /dev/null +++ b/.github/workflows/autoclose_comment.yml @@ -0,0 +1,80 @@ +--- +name: autoclose comment +# Post comment on PRs when labeled with "status: autoclose candidate". +# Based on scikit-learn's autoclose bot at +# https://github.com/scikit-learn/scikit-learn/blob/main/.github/workflows/autoclose-comment.yml + +permissions: + contents: read + pull-requests: write + +on: + pull_request_target: + types: + - labeled + +env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_REPO: ${{ github.repository }} + PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }} + +jobs: + post_comment: + name: post_comment + if: "${{ contains(github.event.label.name, 'status: autoclose candidate') }}" + runs-on: ubuntu-latest + + steps: + + - name: comment on potential autoclose + run: | + gh api \ + --method POST \ + -H "Accept: application/vnd.github+json" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + /repos/$GH_REPO/issues/$PULL_REQUEST_NUMBER/comments \ + -f "body=$BODY" + env: + BODY: > + ⏰ This pull request might be automatically closed in two weeks from now. + + + Thank you for your contribution to Matplotlib and for the effort you + have put into this PR. This pull request does not yet meet the + quality and clarity standards needed for an effective review. + Project maintainers have limited time for code reviews, and our goal + is to prioritize well-prepared contributions to keep Matplotlib + maintainable. + + + Matplotlib maintainers cannot provide one-to-one guidance on this PR. + However, if you ask focused, well-researched questions, a community + member may be willing to help. 💬 + + + To increase the chance of a productive review: + + - Use [the template provided in the PR + description](https://github.com/matplotlib/matplotlib/blob/main/.github/PULL_REQUEST_TEMPLATE.md) + and fill it out as completely as possible, especially the summary and AI Disclosure sections. + + - Make sure your PR conforms to our [PR + checklist](https://matplotlib.org/devdocs/devel/pr_guide.html#summary-for-pull-request-authors). + + + As the author, you are responsible for driving this PR, which entails doing + necessary background research as well as presenting its context and your + thought process. If you are a new contributor, or do not know how to + fulfill these requirements, we recommend that you familiarize + yourself with Matplotlib's + [development conventions](https://matplotlib.org/devdocs/devel/index.html) + or engage with the community via our [Discourse](https://discourse.matplotlib.org/) + or one of our [meetings](https://scientific-python.org/calendars/) + before submitting code. + + + If you substantially improve this PR within two weeks, leave a comment + and a team member may remove the `status: autoclose candidate` label and the + PR stays open. Cosmetic changes or incomplete fixes will not be + sufficient. Maintainers will assess improvements on their own + schedule. Please do not ping (`@`) maintainers. diff --git a/.github/workflows/autoclose_schedule.yml b/.github/workflows/autoclose_schedule.yml new file mode 100644 index 000000000000..ddce7e365f9f --- /dev/null +++ b/.github/workflows/autoclose_schedule.yml @@ -0,0 +1,37 @@ +--- +name: autoclose schedule +# Autoclose PRs labeled with "status: autoclose candidate" after 2 weeks. +# Based on scikit-learn's implementation at +# https://github.com/scikit-learn/scikit-learn/blob/main/.github/workflows/autoclose-schedule.yml + +permissions: + contents: read + pull-requests: write + +on: + schedule: + - cron: '0 2 * * *' # runs daily at 02:00 UTC + workflow_dispatch: + +env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + +jobs: + + autoclose: + name: autoclose labeled PRs + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + - uses: actions/setup-python@v6 + with: + python-version: '3.13' + - name: Install PyGithub + run: pip install -Uq PyGithub + + - name: Checkout repository + uses: actions/checkout@v5 + + - name: Close PRs labeled more than 14 days ago + run: | + python tools/autoclose_prs.py diff --git a/.github/workflows/cibuildwheel.yml b/.github/workflows/cibuildwheel.yml index cd9ad2161595..0d514a5c8bdc 100644 --- a/.github/workflows/cibuildwheel.yml +++ b/.github/workflows/cibuildwheel.yml @@ -18,35 +18,39 @@ on: - reopened - labeled -permissions: - contents: read +permissions: {} jobs: build_sdist: if: >- - 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') + 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-20.04 + runs-on: ubuntu-latest + permissions: + contents: read outputs: SDIST_NAME: ${{ steps.sdist.outputs.SDIST_NAME }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 + persist-credentials: false - - uses: actions/setup-python@v5 + - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 name: Install Python with: - python-version: '3.10' + python-version: '3.11' # Something changed somewhere that prevents the downloaded-at-build-time # licenses from being included in built wheels, so pre-download them so @@ -69,7 +73,7 @@ jobs: run: twine check dist/* - name: Upload sdist result - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 with: name: cibw-sdist path: dist/*.tar.gz @@ -77,17 +81,21 @@ jobs: build_wheels: if: >- - 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') + 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 }} + permissions: + contents: read runs-on: ${{ matrix.os }} env: CIBW_BEFORE_BUILD: >- @@ -114,54 +122,50 @@ jobs: CIBW_TEST_COMMAND: >- python {package}/ci/check_version_number.py MACOSX_DEPLOYMENT_TARGET: "10.12" - MPL_DISABLE_FH4: "yes" strategy: matrix: include: - - os: ubuntu-20.04 + - os: ubuntu-latest cibw_archs: "x86_64" - - os: ubuntu-20.04 + - os: ubuntu-24.04-arm cibw_archs: "aarch64" - os: windows-latest - cibw_archs: "auto64" - - os: macos-13 + cibw_archs: "AMD64" + - os: windows-11-arm + cibw_archs: "ARM64" + - os: macos-15-intel cibw_archs: "x86_64" - os: macos-14 cibw_archs: "arm64" steps: - - name: Set up QEMU - if: matrix.cibw_archs == 'aarch64' - uses: docker/setup-qemu-action@v3 - with: - platforms: arm64 - - name: Download sdist - uses: actions/download-artifact@v4 + uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0 with: name: cibw-sdist path: dist/ + - name: Build wheels for CPython 3.14 + uses: pypa/cibuildwheel@ee02a1537ce3071a004a6b08c41e72f0fdc42d9a # v3.4.0 + with: + package-dir: dist/${{ needs.build_sdist.outputs.SDIST_NAME }} + env: + CIBW_BUILD: "cp314-* cp314t-*" + CIBW_ENABLE: "cpython-freethreading cpython-prerelease" + CIBW_ARCHS: ${{ matrix.cibw_archs }} + CIBW_MANYLINUX_X86_64_IMAGE: manylinux_2_28 + - name: Build wheels for CPython 3.13 - uses: pypa/cibuildwheel@7940a4c0e76eb2030e473a5f864f291f63ee879b # v2.21.3 + uses: pypa/cibuildwheel@ee02a1537ce3071a004a6b08c41e72f0fdc42d9a # v3.4.0 with: package-dir: dist/${{ needs.build_sdist.outputs.SDIST_NAME }} env: CIBW_BUILD: "cp313-* cp313t-*" - CIBW_BUILD_FRONTEND: - "pip; args: --pre --extra-index-url https://pypi.anaconda.org/scientific-python-nightly-wheels/simple" - CIBW_FREE_THREADED_SUPPORT: true - # No free-threading wheels available for aarch64 on Pillow. - CIBW_TEST_SKIP: "cp313t-manylinux_aarch64" - # We need pre-releases to get the nightly wheels. - CIBW_BEFORE_TEST: >- - pip install --pre - --extra-index-url https://pypi.anaconda.org/scientific-python-nightly-wheels/simple - contourpy numpy pillow + CIBW_ENABLE: cpython-freethreading CIBW_ARCHS: ${{ matrix.cibw_archs }} - name: Build wheels for CPython 3.12 - uses: pypa/cibuildwheel@7940a4c0e76eb2030e473a5f864f291f63ee879b # v2.21.3 + uses: pypa/cibuildwheel@ee02a1537ce3071a004a6b08c41e72f0fdc42d9a # v3.4.0 with: package-dir: dist/${{ needs.build_sdist.outputs.SDIST_NAME }} env: @@ -169,69 +173,25 @@ jobs: CIBW_ARCHS: ${{ matrix.cibw_archs }} - name: Build wheels for CPython 3.11 - uses: pypa/cibuildwheel@7940a4c0e76eb2030e473a5f864f291f63ee879b # v2.21.3 + uses: pypa/cibuildwheel@ee02a1537ce3071a004a6b08c41e72f0fdc42d9a # v3.4.0 with: package-dir: dist/${{ needs.build_sdist.outputs.SDIST_NAME }} env: CIBW_BUILD: "cp311-*" CIBW_ARCHS: ${{ matrix.cibw_archs }} - - name: Build wheels for CPython 3.10 - uses: pypa/cibuildwheel@7940a4c0e76eb2030e473a5f864f291f63ee879b # v2.21.3 - with: - package-dir: dist/${{ needs.build_sdist.outputs.SDIST_NAME }} - env: - CIBW_BUILD: "cp310-*" - CIBW_ARCHS: ${{ matrix.cibw_archs }} - - name: Build wheels for PyPy - uses: pypa/cibuildwheel@7940a4c0e76eb2030e473a5f864f291f63ee879b # v2.21.3 + uses: pypa/cibuildwheel@ee02a1537ce3071a004a6b08c41e72f0fdc42d9a # v3.4.0 with: package-dir: dist/${{ needs.build_sdist.outputs.SDIST_NAME }} env: - CIBW_BUILD: "pp310-*" + CIBW_BUILD: "pp311-*" CIBW_ARCHS: ${{ matrix.cibw_archs }} - # Work around for https://github.com/pypa/setuptools/issues/4571 - # This can be removed once kiwisolver has wheels for PyPy 3.10 - # https://github.com/nucleic/kiwi/pull/182 - CIBW_BEFORE_TEST: >- - export PIP_CONSTRAINT=pypy-constraint.txt && - echo "setuptools!=72.2.0" > $PIP_CONSTRAINT && - pip install kiwisolver && - unset PIP_CONSTRAINT - if: matrix.cibw_archs != 'aarch64' && matrix.os != 'windows-latest' - - - uses: actions/upload-artifact@v4 + CIBW_ENABLE: pypy + if: matrix.cibw_archs != 'aarch64' && matrix.os != 'windows-latest' && matrix.os != 'windows-11-arm' + + - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 with: name: cibw-wheels-${{ runner.os }}-${{ matrix.cibw_archs }} path: ./wheelhouse/*.whl if-no-files-found: error - - publish: - if: 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@v4 - with: - 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@1c608d11d69870c2092266b3f9a6f3abbf17002c # v1.4.3 - with: - subject-path: dist/matplotlib-* - - - name: Publish package distributions to PyPI - uses: pypa/gh-action-pypi-publish@f7600683efdcb7656dec5b29656edb7bc586e597 # v1.10.3 diff --git a/.github/workflows/circleci.yml b/.github/workflows/circleci.yml index a64b312e8246..49dc4ea3b3ec 100644 --- a/.github/workflows/circleci.yml +++ b/.github/workflows/circleci.yml @@ -1,6 +1,9 @@ --- name: "CircleCI artifact handling" on: [status] + +permissions: {} + jobs: circleci_artifacts_redirector_job: if: "${{ github.event.context == 'ci/circleci: docs-python3' }}" @@ -11,7 +14,7 @@ jobs: steps: - name: GitHub Action step uses: - scientific-python/circleci-artifacts-redirector-action@4e13a10d89177f4bfc8007a7064bdbeda848d8d1 # v1.0.0 + scientific-python/circleci-artifacts-redirector-action@5d358ff96e96429a5c64a969bb4a574555439f4f # v1.3.1 with: repo-token: ${{ secrets.GITHUB_TOKEN }} api-token: ${{ secrets.CIRCLECI_TOKEN }} @@ -28,16 +31,20 @@ jobs: runs-on: ubuntu-latest name: Post warnings/errors as review steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.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 "${{ github.event.target_url }}" + python .circleci/fetch_doc_logs.py "${target_url}" - name: Set up reviewdog if: "${{ steps.fetch-artifacts.outputs.count != 0 }}" - uses: reviewdog/action-setup@v1 + uses: reviewdog/action-setup@d8a7baabd7f3e8544ee4dbde3ee41d0011c3a93f # v1.5.0 with: reviewdog_version: latest diff --git a/.github/workflows/clean_pr.yml b/.github/workflows/clean_pr.yml index 77e49f7c1d9e..75f6a451c7ee 100644 --- a/.github/workflows/clean_pr.yml +++ b/.github/workflows/clean_pr.yml @@ -2,17 +2,19 @@ name: PR cleanliness on: [pull_request] -permissions: - contents: read +permissions: {} jobs: pr_clean: runs-on: ubuntu-latest + permissions: + contents: read steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: '0' + persist-credentials: false - name: Check for added-and-deleted files run: | git fetch --quiet origin "$GITHUB_BASE_REF" diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 203b0eee9ca4..93eefd6ca27f 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -10,8 +10,11 @@ on: schedule: - cron: '45 19 * * 1' +permissions: {} + jobs: analyze: + if: github.repository == 'matplotlib/matplotlib' name: Analyze runs-on: ubuntu-latest permissions: @@ -26,10 +29,12 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - name: Initialize CodeQL - uses: github/codeql-action/init@v3 + uses: github/codeql-action/init@0d579ffd059c29b07949a3cce3983f0780820c98 # v4.32.6 with: languages: ${{ matrix.language }} @@ -40,4 +45,4 @@ jobs: pip install --user -v . - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v3 + uses: github/codeql-action/analyze@0d579ffd059c29b07949a3cce3983f0780820c98 # v4.32.6 diff --git a/.github/workflows/conflictcheck.yml b/.github/workflows/conflictcheck.yml index 3110839e5150..2058da8ca9fb 100644 --- a/.github/workflows/conflictcheck.yml +++ b/.github/workflows/conflictcheck.yml @@ -9,15 +9,17 @@ on: pull_request_target: types: [synchronize] -permissions: - pull-requests: write +permissions: {} 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@1b1b1fcde06a9b3d089f3464c96417961dde1168 # v3.0.2 + uses: eps1lon/actions-label-merge-conflict@1df065ebe6e3310545d4f4c4e862e43bdca146f0 # v3.0.3 with: dirtyLabel: "status: needs rebase" repoToken: "${{ secrets.GITHUB_TOKEN }}" diff --git a/.github/workflows/cygwin.yml b/.github/workflows/cygwin.yml index 5dee68597d5c..8a01d76c00f5 100644 --- a/.github/workflows/cygwin.yml +++ b/.github/workflows/cygwin.yml @@ -30,8 +30,7 @@ on: - cron: "47 5 * * 6" workflow_dispatch: -permissions: - contents: read +permissions: {} env: NO_AT_BRIDGE: 1 # Necessary for GTK3 interactive test. @@ -47,6 +46,8 @@ jobs: test-cygwin: runs-on: windows-latest + permissions: + contents: read name: Python 3.${{ matrix.python-minor-version }} on Cygwin # Enable these when Cygwin has Python 3.12. if: >- @@ -79,11 +80,12 @@ jobs: - name: Fix line endings run: git config --global core.autocrlf input - - uses: actions/checkout@v4 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 + persist-credentials: false - - uses: cygwin/cygwin-install-action@v4 + - uses: cygwin/cygwin-install-action@711d29f3da23c9f4a1798e369a6f01198c13b11a # v6 with: packages: >- ccache gcc-g++ gdb git graphviz libcairo-devel libffi-devel @@ -139,21 +141,21 @@ jobs: # FreeType build fails with bash, succeeds with dash - name: Cache pip - uses: actions/cache@v4 + uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.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@v4 + uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.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@v4 + uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: path: | C:\cygwin\home\runneradmin\.cache\matplotlib diff --git a/.github/workflows/do_not_merge.yml b/.github/workflows/do_not_merge.yml index dde5bfb5ec81..0c263623942b 100644 --- a/.github/workflows/do_not_merge.yml +++ b/.github/workflows/do_not_merge.yml @@ -15,7 +15,8 @@ jobs: 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')}} + contains(github.event.pull_request.labels.*.name, 'status: waiting for other PR') || + contains(github.event.pull_request.labels.*.name, 'DO NOT MERGE') }} steps: - name: Check for label if: ${{'true' == env.has_tag}} @@ -23,7 +24,7 @@ jobs: 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" - echo "${{env.has_tag}}" + echo "* DO NOT MERGE" exit 1 - name: Allow merging if: ${{'false' == env.has_tag}} diff --git a/.github/workflows/good-first-issue.yml b/.github/workflows/good-first-issue.yml index 8905511fc01d..6543f05a0837 100644 --- a/.github/workflows/good-first-issue.yml +++ b/.github/workflows/good-first-issue.yml @@ -4,6 +4,9 @@ on: issues: types: - labeled + +permissions: {} + jobs: add-comment: if: github.event.label.name == 'Good first issue' @@ -12,19 +15,22 @@ jobs: issues: write steps: - name: Add comment - uses: peter-evans/create-or-update-comment@v4 + uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9 # v5.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). + This issue is suited to new contributors because it does not require + understanding of the Matplotlib internals. This is a non-urgent task + intended for human contributors to learn how to contribute; therefore please + do not try to automate a solution using AI. 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. + **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 index dc7a0716bfe8..2914c64a8461 100644 --- a/.github/workflows/labeler.yml +++ b/.github/workflows/labeler.yml @@ -3,6 +3,8 @@ name: "Pull Request Labeler" on: - pull_request_target +permissions: {} + jobs: labeler: permissions: @@ -10,6 +12,6 @@ jobs: pull-requests: write runs-on: ubuntu-latest steps: - - uses: actions/labeler@v5 + - uses: actions/labeler@634933edcd8ababfe52f92936142cc22ac488b1b # v6.0.1 with: sync-labels: true diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml new file mode 100644 index 000000000000..048b78f24761 --- /dev/null +++ b/.github/workflows/linting.yml @@ -0,0 +1,104 @@ +--- +name: Linting +on: [pull_request] + +permissions: {} + +jobs: + pre-commit: + name: precommit + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 0 + persist-credentials: false + - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 + with: + python-version: "3.x" + - uses: j178/prek-action@0bb87d7f00b0c99306c8bcb8b8beba1eb581c037 # v1.1.1 + with: + extra_args: --hook-stage manual --all-files + + ruff: + name: ruff + runs-on: ubuntu-latest + permissions: + contents: read + checks: write + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + + - name: Set up Python 3 + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 + with: + python-version: '3.11' + + - name: Install ruff + run: pip3 install ruff + + - name: Set up reviewdog + uses: reviewdog/action-setup@d8a7baabd7f3e8544ee4dbde3ee41d0011c3a93f # v1.5.0 + + - name: Run ruff + env: + REVIEWDOG_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + 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: + contents: read + checks: write + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + + - name: Set up Python 3 + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.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@d8a7baabd7f3e8544ee4dbde3ee41d0011c3a93f # v1.5.0 + + - name: Run mypy + env: + REVIEWDOG_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + set -o pipefail + mypy --config pyproject.toml | \ + reviewdog -f=mypy -name=mypy \ + -tee -reporter=github-check -filter-mode nofilter + + + eslint: + name: eslint + runs-on: ubuntu-latest + permissions: + contents: read + checks: write + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + + - name: eslint + uses: reviewdog/action-eslint@556a3fdaf8b4201d4d74d406013386aa4f7dab96 # v1.34.0 + with: + filter_mode: nofilter + github_token: ${{ secrets.GITHUB_TOKEN }} + reporter: github-check + workdir: 'lib/matplotlib/backends/web_backend/' diff --git a/.github/workflows/mypy-stubtest.yml b/.github/workflows/mypy-stubtest.yml index 5b29a93b7533..81fcd48462e8 100644 --- a/.github/workflows/mypy-stubtest.yml +++ b/.github/workflows/mypy-stubtest.yml @@ -2,24 +2,27 @@ name: Mypy Stubtest on: [pull_request] -permissions: - contents: read - checks: write +permissions: {} jobs: mypy-stubtest: name: mypy-stubtest runs-on: ubuntu-latest + permissions: + contents: read + checks: write steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - name: Set up Python 3 - uses: actions/setup-python@v5 + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: - python-version: '3.10' + python-version: '3.11' - name: Set up reviewdog - uses: reviewdog/action-setup@v1 + uses: reviewdog/action-setup@d8a7baabd7f3e8544ee4dbde3ee41d0011c3a93f # v1.5.0 - name: Install tox run: python -m pip install tox @@ -30,7 +33,7 @@ jobs: run: | set -o pipefail tox -e stubtest | \ - sed -e "s!.tox/stubtest/lib/python3.10/site-packages!lib!g" | \ + sed -e "s!.tox/stubtest/lib/python3.11/site-packages!lib!g" | \ reviewdog \ -efm '%Eerror: %m' \ -efm '%CStub: in file %f:%l' \ diff --git a/.github/workflows/nightlies.yml b/.github/workflows/nightlies.yml index 25e2bb344eda..661234c3e16f 100644 --- a/.github/workflows/nightlies.yml +++ b/.github/workflows/nightlies.yml @@ -8,8 +8,7 @@ on: # Run on demand with workflow dispatch workflow_dispatch: -permissions: - actions: read +permissions: {} jobs: upload_nightly_wheels: @@ -21,6 +20,8 @@ jobs: # to work in subsequent jobs. # https://github.com/mamba-org/setup-micromamba#about-login-shells shell: bash -e -l {0} + permissions: + actions: read if: github.repository_owner == 'matplotlib' steps: @@ -31,35 +32,35 @@ jobs: 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}" + for WORKFLOW_NAME in cibuildwheel.yml wasm.yml; do + 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}" + done mkdir dist mv ${ARTIFACT_PATTERN}/*.whl dist/ ls -l dist/ - name: Upload wheels to Anaconda Cloud as nightlies - uses: scientific-python/upload-nightly-action@82396a2ed4269ba06c6b2988bb4fd568ef3c3d6b # 0.6.1 + uses: scientific-python/upload-nightly-action@5748273c71e2d8d3a61f3a11a16421c8954f9ecf # 0.6.3 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 index 533f676a0fab..f5fea030e4eb 100644 --- a/.github/workflows/pr_welcome.yml +++ b/.github/workflows/pr_welcome.yml @@ -1,27 +1,35 @@ --- name: PR Greetings -on: [pull_request_target] +on: + pull_request_target: + types: opened + issues: + types: opened -permissions: - pull-requests: write +permissions: {} jobs: greeting: runs-on: ubuntu-latest - + permissions: + issues: write + pull-requests: write steps: - - uses: actions/first-interaction@v1 + - uses: plbstl/first-contribution@4fb1541ce2706255850d56c5684552607be1ae9b # v4.2.0 with: - repo-token: ${{ secrets.GITHUB_TOKEN }} - pr-message: >+ + labels: first-contribution + pr-opened-msg: >+ 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. + through the cracks. We also ask that you please finish addressing + any review comments on this PR and wait for it to be merged (or + closed) before opening a new one, as it can be a valuable learning + experience to go through the review process. You can also join us [on @@ -31,9 +39,13 @@ jobs: For details on testing, writing docs, and our review process, please see [the developer - guide](https://matplotlib.org/devdocs/devel/index.html) + guide](https://matplotlib.org/devdocs/devel/index.html). + + **Please let us know if (and how) you use AI, it will help us give you + better feedback on your PR.** 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). + issue-opened-msg: "" diff --git a/.github/workflows/reviewdog.yml b/.github/workflows/reviewdog.yml deleted file mode 100644 index 12b59d866e42..000000000000 --- a/.github/workflows/reviewdog.yml +++ /dev/null @@ -1,75 +0,0 @@ ---- -name: Linting -on: [pull_request] - -permissions: - contents: read - checks: write - pull-requests: write - -jobs: - flake8: - name: flake8 - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Set up Python 3 - uses: actions/setup-python@v5 - with: - python-version: '3.10' - - - name: Install flake8 - run: pip3 install -r requirements/testing/flake8.txt - - - name: Set up reviewdog - uses: reviewdog/action-setup@v1 - - - name: Run flake8 - env: - REVIEWDOG_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - set -o pipefail - flake8 --docstring-convention=all | \ - reviewdog -f=pep8 -name=flake8 \ - -tee -reporter=github-check -filter-mode nofilter - mypy: - name: mypy - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Set up Python 3 - uses: actions/setup-python@v5 - with: - python-version: '3.10' - - - name: Install mypy - run: pip3 install -r requirements/testing/mypy.txt -r requirements/testing/all.txt - - - name: Set up reviewdog - uses: reviewdog/action-setup@v1 - - - name: Run mypy - env: - REVIEWDOG_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - set -o pipefail - mypy --config pyproject.toml | \ - reviewdog -f=mypy -name=mypy \ - -tee -reporter=github-check -filter-mode nofilter - - - eslint: - name: eslint - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: eslint - uses: reviewdog/action-eslint@v1 - with: - filter_mode: nofilter - github_token: ${{ secrets.GITHUB_TOKEN }} - reporter: github-check - workdir: 'lib/matplotlib/backends/web_backend/' diff --git a/.github/workflows/stale-tidy.yml b/.github/workflows/stale-tidy.yml index 92a81ee856e4..feb1fe701d70 100644 --- a/.github/workflows/stale-tidy.yml +++ b/.github/workflows/stale-tidy.yml @@ -4,12 +4,16 @@ on: schedule: - cron: '30 1 * * 2,4,6' +permissions: {} + jobs: stale: if: github.repository == 'matplotlib/matplotlib' runs-on: ubuntu-latest + permissions: + issues: write steps: - - uses: actions/stale@v9 + - uses: actions/stale@b5d41d4e1d5dceea10e7104786b73624c18a190f # v10.2.0 with: repo-token: ${{ secrets.GITHUB_TOKEN }} operations-per-run: 300 @@ -20,5 +24,5 @@ jobs: close-issue-label: "status: closed as inactive" days-before-issue-close: 30 ascending: true - exempt-issue-labels: "keep" + exempt-issue-labels: "keep,status: confirmed bug" exempt-pr-labels: "keep,status: orphaned PR" diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index c606d4288bd2..63f1a1ce3b05 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -2,14 +2,19 @@ name: 'Label inactive PRs' on: schedule: - - cron: '30 1 * * 1,3,5' + - cron: '30 1 * * 1' + +permissions: {} jobs: stale: if: github.repository == 'matplotlib/matplotlib' runs-on: ubuntu-latest + permissions: + issues: write + pull-requests: write steps: - - uses: actions/stale@v9 + - uses: actions/stale@b5d41d4e1d5dceea10e7104786b73624c18a190f # v10.2.0 with: repo-token: ${{ secrets.GITHUB_TOKEN }} operations-per-run: 20 @@ -36,3 +41,4 @@ jobs: ascending: true exempt-issue-labels: "keep" exempt-pr-labels: "keep,status: orphaned PR" + sort-by: updated diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index f73fde457be3..1e79166758d1 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -22,6 +22,8 @@ on: - cron: "47 5 * * 6" workflow_dispatch: +permissions: {} + env: NO_AT_BRIDGE: 1 # Necessary for GTK3 interactive test. OPENBLAS_NUM_THREADS: 1 @@ -48,87 +50,64 @@ jobs: matrix: include: - name-suffix: "(Minimum Versions)" - os: ubuntu-20.04 - python-version: '3.10' + os: ubuntu-22.04 + python-version: '3.11' extra-requirements: '-c requirements/testing/minver.txt' delete-font-cache: true - # Oldest versions with Py3.10 wheels. - pyqt5-ver: '==5.15.5 sip==6.3.0' - pyqt6-ver: '==6.2.0 PyQt6-Qt6==6.2.0' - pyside2-ver: '==5.15.2.1' - pyside6-ver: '==6.2.0' - - os: ubuntu-20.04 - python-version: '3.10' - # One CI run tests ipython/matplotlib-inline before backend mapping moved to mpl - extra-requirements: - -r requirements/testing/extra.txt - "ipython==7.29.0" - "ipykernel==5.5.6" - "matplotlib-inline<0.1.7" - CFLAGS: "-fno-lto" # Ensure that disabling LTO works. - # https://github.com/matplotlib/matplotlib/pull/26052#issuecomment-1574595954 - # https://www.riverbankcomputing.com/pipermail/pyqt/2023-November/045606.html - pyqt6-ver: '!=6.5.1,!=6.6.0,!=6.7.1' - # https://bugreports.qt.io/projects/PYSIDE/issues/PYSIDE-2346 - pyside6-ver: '!=6.5.1' + # https://github.com/matplotlib/matplotlib/issues/29844 + pygobject-ver: '<3.52.0' - os: ubuntu-22.04 python-version: '3.11' - # https://www.riverbankcomputing.com/pipermail/pyqt/2023-November/045606.html - pyqt6-ver: '!=6.6.0' - # https://bugreports.qt.io/projects/PYSIDE/issues/PYSIDE-2346 - pyside6-ver: '!=6.5.1' + CFLAGS: "-fno-lto" # Ensure that disabling LTO works. extra-requirements: '-r requirements/testing/extra.txt' - - os: ubuntu-22.04 - python-version: '3.12' - # https://www.riverbankcomputing.com/pipermail/pyqt/2023-November/045606.html - pyqt6-ver: '!=6.6.0' - # https://bugreports.qt.io/projects/PYSIDE/issues/PYSIDE-2346 - pyside6-ver: '!=6.5.1' - - os: ubuntu-22.04 + # 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' - # https://www.riverbankcomputing.com/pipermail/pyqt/2023-November/045606.html - pyqt6-ver: '!=6.6.0' - # https://bugreports.qt.io/projects/PYSIDE/issues/PYSIDE-2346 - pyside6-ver: '!=6.5.1' + 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://www.riverbankcomputing.com/pipermail/pyqt/2023-November/045606.html - pyqt6-ver: '!=6.6.0' - # https://bugreports.qt.io/projects/PYSIDE/issues/PYSIDE-2346 - pyside6-ver: '!=6.5.1' - - os: macos-13 # This runner is on Intel chips. - python-version: '3.10' - # https://bugreports.qt.io/projects/PYSIDE/issues/PYSIDE-2346 - pyside6-ver: '!=6.5.1' - - os: macos-14 # This runner is on M1 (arm64) chips. + # https://github.com/matplotlib/matplotlib/issues/29844 + pygobject-ver: '<3.52.0' + - os: ubuntu-24.04 + python-version: '3.12' + - os: ubuntu-24.04 + python-version: '3.14' + - os: ubuntu-24.04-arm python-version: '3.12' - # https://bugreports.qt.io/projects/PYSIDE/issues/PYSIDE-2346 - pyside6-ver: '!=6.5.1' - os: macos-14 # This runner is on M1 (arm64) chips. + python-version: '3.11' + # 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.12' + # https://github.com/matplotlib/matplotlib/issues/29732 + pygobject-ver: '<3.52.0' + - os: macos-15 # This runner is on M1 (arm64) chips. python-version: '3.13' - # https://bugreports.qt.io/projects/PYSIDE/issues/PYSIDE-2346 - pyside6-ver: '!=6.5.1' + # https://github.com/matplotlib/matplotlib/issues/29732 + pygobject-ver: '<3.52.0' + - os: macos-15 # This runner is on M1 (arm64) chips. + python-version: '3.14' + # https://github.com/matplotlib/matplotlib/issues/29732 + pygobject-ver: '<3.52.0' steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 + persist-credentials: false - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 - if: matrix.python-version != '3.13t' + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: python-version: ${{ matrix.python-version }} allow-prereleases: true - - name: Set up Python ${{ matrix.python-version }} - uses: deadsnakes/action@e640ac8743173a67cca4d7d77cd837e514bf98e8 # v3.2.0 - if: matrix.python-version == '3.13t' - with: - python-version: '3.13' - nogil: true - - name: Install OS dependencies run: | case "${{ runner.os }}" in @@ -139,12 +118,12 @@ jobs: ccache \ cm-super \ dvipng \ - ffmpeg \ fonts-freefont-otf \ fonts-noto-cjk \ fonts-wqy-zenhei \ gdb \ gir1.2-gtk-3.0 \ + gir1.2-gtk-4.0 \ graphviz \ inkscape \ language-pack-de \ @@ -153,7 +132,7 @@ jobs: libcairo2-dev \ libffi-dev \ libgeos-dev \ - libgirepository1.0-dev \ + libnotify4 \ libsdl2-2.0-0 \ libxkbcommon-x11-0 \ libxcb-cursor0 \ @@ -173,17 +152,17 @@ jobs: texlive-latex-recommended \ texlive-luatex \ texlive-pictures \ - texlive-xetex - if [[ "${{ matrix.python-version }}" = '3.13t' ]]; then - # TODO: Remove this once setup-python supports nogil distributions. - sudo apt-get install -yy --no-install-recommends \ - python3.13-tk-nogil + 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-20.04 ]]; then - sudo apt-get install -yy --no-install-recommends libopengl0 - else # ubuntu-22.04 + if [[ "${{ matrix.os }}" = ubuntu-22.04 ]]; then sudo apt-get install -yy --no-install-recommends \ - gir1.2-gtk-4.0 libnotify4 + libgirepository1.0-dev + else # ubuntu-24.04 + sudo apt-get install -yy --no-install-recommends \ + libgirepository-2.0-dev fi ;; macOS) @@ -199,13 +178,15 @@ jobs: brew unlink ${python_package} brew link --overwrite ${python_package} done - brew install ccache ghostscript gobject-introspection gtk4 ninja - brew install --cask font-noto-sans-cjk inkscape + # 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@v4 + uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 if: startsWith(runner.os, 'Linux') with: path: ~/.cache/pip @@ -213,7 +194,7 @@ jobs: restore-keys: | ${{ matrix.os }}-py${{ matrix.python-version }}-pip- - name: Cache pip - uses: actions/cache@v4 + uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 if: startsWith(runner.os, 'macOS') with: path: ~/Library/Caches/pip @@ -221,7 +202,7 @@ jobs: restore-keys: | ${{ matrix.os }}-py${{ matrix.python-version }}-pip- - name: Cache ccache - uses: actions/cache@v4 + uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: path: | ~/.ccache @@ -229,25 +210,16 @@ jobs: restore-keys: | ${{ matrix.os }}-py${{ matrix.python-version }}-ccache- - name: Cache Matplotlib - uses: actions/cache@v4 + uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: path: | ~/.cache/matplotlib !~/.cache/matplotlib/tex.cache !~/.cache/matplotlib/test_cache - key: 4-${{ runner.os }}-py${{ matrix.python-version }}-mpl-${{ github.ref }}-${{ github.sha }} + key: 6-${{ matrix.os }}-py${{ matrix.python-version }}-mpl-${{ github.ref }}-${{ github.sha }} restore-keys: | - 4-${{ runner.os }}-py${{ matrix.python-version }}-mpl-${{ github.ref }}- - 4-${{ runner.os }}-py${{ matrix.python-version }}-mpl- - - - name: Install the nightly dependencies - if: matrix.python-version == '3.13t' - run: | - python -m pip install pytz tzdata python-dateutil # Must be installed for Pandas. - python -m pip install \ - --pre \ - --index-url https://pypi.anaconda.org/scientific-python-nightly-wheels/simple \ - --upgrade --only-binary=:all: numpy pandas pillow contourpy + 6-${{ matrix.os }}-py${{ matrix.python-version }}-mpl-${{ github.ref }}- + 6-${{ matrix.os }}-py${{ matrix.python-version }}-mpl- - name: Install Python dependencies run: | @@ -281,7 +253,7 @@ jobs: # (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 && + 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' @@ -290,30 +262,33 @@ jobs: echo 'PyGObject 3 is available' || echo 'PyGObject 3 is not available' ) - python -mpip install --upgrade pyqt5${{ matrix.pyqt5-ver }} && - python -c 'import PyQt5.QtCore' && - echo 'PyQt5 is available' || - echo 'PyQt5 is not available' + # PyQt5 does not have any wheels for ARM on Linux. + if [[ "${{ matrix.os }}" != 'ubuntu-24.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${{ matrix.pyside2-ver }} && + if [[ "${{ matrix.os }}" != 'macos-14' && "${{ matrix.python-version }}" == '3.11' + ]]; 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 pyqt6${{ matrix.pyqt6-ver }} && + 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 pyside6${{ matrix.pyside6-ver }} && + 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 \ + python -mpip install --upgrade --only-binary :all: \ -f "https://extras.wxpython.org/wxPython4/extras/linux/gtk3/${{ matrix.os }}" \ wxPython && python -c 'import wx' && @@ -370,32 +345,29 @@ jobs: - 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 + find ./result_images -name "*-expected*.png" | while read file; do + if [[ $file == *-expected_???.png ]]; then + extension=${file: -7:3} + base=${file%*-expected_$extension.png}_$extension + else + extension="png" + base=${file%-expected.png} + fi + if [[ ! -e ${base}-failed-diff.png ]]; then + indent="" + list=($file $base.png) + if [[ $extension != "png" ]]; then + list+=(${base%_$extension}-expected.$extension ${base%_$extension}.$extension) + fi + for to_remove in "${list[@]}"; do + if [[ -e $to_remove ]]; then + rm $to_remove + echo "${indent}Removed $to_remove" + fi + indent+=" " done - } - - remove_files "png"; remove_files "svg"; remove_files "pdf"; remove_files "eps"; + fi + done if [ "$(find ./result_images -mindepth 1 -type d)" ]; then find ./result_images/* -type d -empty -delete @@ -405,11 +377,17 @@ jobs: if: ${{ !cancelled() && github.event_name != 'schedule' }} run: | if [[ "${{ runner.os }}" != 'macOS' ]]; then - lcov --rc lcov_branch_coverage=1 --capture --directory . \ - --output-file coverage.info - lcov --rc lcov_branch_coverage=1 --output-file coverage.info \ - --extract coverage.info $PWD/src/'*' $PWD/lib/'*' - lcov --rc lcov_branch_coverage=1 --list coverage.info + LCOV_IGNORE_ERRORS=',' # do not ignore any lcov errors by default + if [[ "${{ matrix.os }}" = ubuntu-24.04 || "${{ matrix.os }}" = ubuntu-24.04-arm ]]; 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 \ @@ -419,12 +397,12 @@ jobs: fi - name: Upload code coverage if: ${{ !cancelled() && github.event_name != 'schedule' }} - uses: codecov/codecov-action@v4 + uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2 with: name: "${{ matrix.python-version }} ${{ matrix.os }} ${{ matrix.name-suffix }}" token: ${{ secrets.CODECOV_TOKEN }} - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 if: failure() with: name: "${{ matrix.python-version }} ${{ matrix.os }} ${{ matrix.name-suffix }} result images" @@ -441,7 +419,7 @@ jobs: steps: - name: Create issue on failure - uses: imjohnbo/issue-bot@v3 + uses: imjohnbo/issue-bot@572eed14422c4d6ca37e870f97e7da209422f5bd # v3.4.4 with: title: "[TST] Upcoming dependency test failures" body: | diff --git a/.github/workflows/wasm.yml b/.github/workflows/wasm.yml new file mode 100644 index 000000000000..49515b7a349a --- /dev/null +++ b/.github/workflows/wasm.yml @@ -0,0 +1,62 @@ +--- +name: Build wasm wheels + +on: + # Save CI by only running this on release branches or tags. + push: + branches: + - 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_wasm: + if: >- + ( + 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 wasm + runs-on: ubuntu-24.04 + + steps: + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + fetch-depth: 0 + persist-credentials: false + + - uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0 + name: Install Python + with: + python-version: '3.13' + + - name: Build wheels for wasm + uses: pypa/cibuildwheel@ee02a1537ce3071a004a6b08c41e72f0fdc42d9a # v3.4.0 + env: + CIBW_BUILD: "cp312-*" + CIBW_PLATFORM: "pyodide" + + - uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 + with: + name: cibw-wheels-wasm + path: ./wheelhouse/*.whl + if-no-files-found: error diff --git a/.gitignore b/.gitignore index b6f9e1ee74f4..0460152a792f 100644 --- a/.gitignore +++ b/.gitignore @@ -44,6 +44,7 @@ pip-wheel-metadata/* .tox # build subproject files subprojects/*/ +subprojects/.* !subprojects/packagefiles/ # OS generated files # @@ -103,6 +104,20 @@ __conda_version__.txt lib/png.lib lib/z.lib +# uv files # +############ +uv.lock + +# Environments # +################ +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + # Jupyter files # ################# diff --git a/.mailmap b/.mailmap index 44005da6e2d8..bbadcb8ff879 100644 --- a/.mailmap +++ b/.mailmap @@ -11,6 +11,8 @@ Alon Hershenhorn Alvaro Sanchez +Andreas C Mueller Andreas Mueller + Andrew Dawson anykraus @@ -32,6 +34,8 @@ Carsten Schelp Casper van der Wel +CharlesHe16 + Chris Holdgraf Cho Yin Yong @@ -57,6 +61,8 @@ David Kua Devashish Deshpande +Diego Leal Petrola + Dietmar Schwertberger Dora Fraeman Caswell @@ -84,16 +90,26 @@ Hajoon Choi hannah +Hannes Breytenbach + Hans Moritz Günther Harshal Prakash Patankar Harshit Patni +Harutaka Kawamura + +Hood Chatham Hood + +Ian Hunt-Isaak + ImportanceOfBeingErnest J. Goutin JGoutin +Jakub Klus <48711526+deep-jkl@users.noreply.github.com> + Jack Kelly Jack Kelly @@ -105,6 +121,8 @@ Jake Vanderplas James R. Evans +Jan-Hendrik Müller <44469195+kolibril13@users.noreply.github.com> + Jeff Lutgen Jeffrey Bingham @@ -112,12 +130,18 @@ Jeffrey Bingham Jens Hedegaard Nielsen Jens Hedegaard Nielsen +Jerome F. Villegas + Joel Frederico <458871+joelfrederico@users.noreply.github.com> +Johan von Forstner + John Hunter Jorrit Wronski +Jose Manuel Martí + Joseph Fox-Rabinovitz Mad Physicist Joseph Fox-Rabinovitz Joseph Fox-Rabinovitz @@ -149,11 +173,15 @@ Leon Yin Lion Krischer +luz paz + Manan Kevadiya Manan Kevadiya <43081866+manan2501@users.noreply.github.com> Manuel Nuno Melo +Marat Kopytjuk + Marco Gorelli Marco Gorelli <33491632+MarcoGorelli@users.noreply.github.com> @@ -215,6 +243,8 @@ Paul Ivanov Per Parker +Péter Leéh + Peter Würtz Peter Würtz @@ -222,12 +252,18 @@ Phil Elson Phil Elson Phil Elson +Philipp Nagel + productivememberofsociety666 none Rishikesh +Ruth Nainggolan Ruth G. N <32371319+ruthgn@users.noreply.github.com> + RyanPan +Richard Sheridan + Samesh Lakhotia Samesh Lakhotia <43701530+sameshl@users.noreply.github.com> ' @@ -236,6 +272,8 @@ Scott Lasley Sebastian Raschka Sebastian Raschka +ShawnChen1996 + Sidharth Bansal Sidharth Bansal <20972099+SidharthBansal@users.noreply.github.com> @@ -257,6 +295,7 @@ Taras Kuzyo Terence Honles +Thomas A Caswell Thomas A Caswell Thomas A Caswell Thomas A Caswell Thomas A Caswell Thomas A Caswell Thomas A Caswell <“tcaswell@gmail.com”> diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6068b8b0df83..4bba747d451b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -5,12 +5,13 @@ ci: exclude: | (?x)^( extern| + subprojects/packagefiles| LICENSE| lib/matplotlib/mpl-data| doc/devel/gitwash| - doc/users/prev| + doc/release/prev| doc/api/prev| - lib/matplotlib/tests/tinypages + lib/matplotlib/tests/data/tinypages ) repos: - repo: https://github.com/pre-commit/pre-commit-hooks @@ -28,7 +29,7 @@ repos: - id: trailing-whitespace exclude_types: [svg] - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.11.2 + rev: v1.15.0 hooks: - id: mypy additional_dependencies: @@ -41,17 +42,16 @@ repos: 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/pycqa/flake8 - rev: 7.1.1 + + - repo: https://github.com/astral-sh/ruff-pre-commit + # Ruff version. + rev: v0.11.5 hooks: - - id: flake8 - additional_dependencies: - - pydocstyle>5.1.0 - - flake8-docstrings>1.4.0 - - flake8-force - args: ["--docstring-convention=all"] + # Run the linter. + - id: ruff + args: [--fix, --show-fixes] - repo: https://github.com/codespell-project/codespell - rev: v2.3.0 + rev: v2.4.1 hooks: - id: codespell files: ^.*\.(py|c|cpp|h|m|md|rst|yml)$ @@ -61,7 +61,7 @@ repos: - "--skip" - "doc/project/credits.rst" - repo: https://github.com/pycqa/isort - rev: 5.13.2 + rev: 6.0.1 hooks: - id: isort name: isort (python) @@ -74,12 +74,12 @@ repos: - sphinx>=1.8.1 - tomli - repo: https://github.com/adrienverge/yamllint - rev: v1.35.1 + rev: v1.37.0 hooks: - id: yamllint args: ["--strict", "--config-file=.yamllint.yml"] - repo: https://github.com/python-jsonschema/check-jsonschema - rev: 0.29.3 + 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 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/README.md b/README.md index 7b9c99597c0d..8f9edaad2b5b 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,7 @@ [![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) +[![LFX Health Score](https://insights.linuxfoundation.org/api/badge/health-score?project=matplotlib)](https://insights.linuxfoundation.org/project/matplotlib) [![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) @@ -14,14 +15,14 @@ [![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 logotype](https://matplotlib.org/stable/_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) +![image](https://matplotlib.org/stable/_static/readme_preview.png) Matplotlib produces publication-quality figures in a variety of hardcopy formats and interactive environments across platforms. Matplotlib can be diff --git a/SECURITY.md b/SECURITY.md index ce022ca60a0f..4400a4501b51 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -8,14 +8,13 @@ versions. | Version | Supported | | ------- | ------------------ | +| 3.10.x | :white_check_mark: | | 3.9.x | :white_check_mark: | -| 3.8.x | :white_check_mark: | +| 3.8.x | :x: | | 3.7.x | :x: | | 3.6.x | :x: | | 3.5.x | :x: | -| 3.4.x | :x: | -| 3.3.x | :x: | -| < 3.3 | :x: | +| < 3.5 | :x: | ## Reporting a Vulnerability diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 4bccb5da8ed2..d68a9d36f0d3 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -49,24 +49,15 @@ stages: - job: Pytest strategy: matrix: - Linux_py310: - vmImage: 'ubuntu-20.04' # keep one job pinned to the oldest image - python.version: '3.10' - Linux_py311: - vmImage: 'ubuntu-latest' - python.version: '3.11' - macOS_py310: - vmImage: 'macOS-latest' - python.version: '3.10' - macOS_py311: - vmImage: 'macOS-latest' - python.version: '3.11' - Windows_py310: - vmImage: 'windows-2019' # keep one job pinned to the oldest image - python.version: '3.10' Windows_py311: - vmImage: 'windows-latest' + 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)' @@ -78,61 +69,7 @@ stages: displayName: 'Use Python $(python.version)' - bash: | - set -e - case "$AGENT_OS" in - Linux) - echo 'Acquire::Retries "3";' | sudo tee /etc/apt/apt.conf.d/80-retries - sudo apt update - sudo apt install --no-install-recommends \ - cm-super \ - dvipng \ - ffmpeg \ - fonts-freefont-otf \ - fonts-noto-cjk \ - fonts-wqy-zenhei \ - gdb \ - gir1.2-gtk-3.0 \ - graphviz \ - inkscape \ - language-pack-de \ - lcov \ - libcairo2 \ - libgirepository-1.0-1 \ - lmodern \ - ninja-build \ - poppler-utils \ - texlive-fonts-recommended \ - texlive-latex-base \ - texlive-latex-extra \ - texlive-latex-recommended \ - texlive-luatex \ - texlive-pictures \ - texlive-xetex - ;; - Darwin) - 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 UsePythonVersion. - for python_package in $(brew list | grep python@); do - brew unlink ${python_package} - brew link --overwrite ${python_package} - done - brew install --cask xquartz - brew install ccache ffmpeg imagemagick mplayer ninja pkg-config - brew install --cask font-noto-sans-cjk-sc - ;; - Windows_NT) - choco install ninja - ;; - *) - exit 1 - ;; - esac + choco install ninja displayName: 'Install dependencies' - bash: | @@ -142,23 +79,9 @@ stages: displayName: 'Install dependencies with pip' - bash: | - case "$AGENT_OS" in - Linux) - export CPPFLAGS='--coverage -fprofile-abs-path' - ;; - Darwin) - export CPPFLAGS='-fprofile-instr-generate=default.%m.profraw' - export CPPFLAGS="$CPPFLAGS -fcoverage-mapping" - ;; - Windows_NT) - CONFIG='--config-settings=setup-args=--vsenv' - CONFIG="$CONFIG --config-settings=setup-args=-Dcpp_link_args=-PROFILE" - CONFIG="$CONFIG --config-settings=setup-args=-Dbuildtype=debug" - ;; - *) - exit 1 - ;; - esac + 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 \ @@ -173,102 +96,52 @@ stages: - bash: | set -e - if [[ "$AGENT_OS" == 'Windows_NT' ]]; then - 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" - if [[ -d $DIR ]]; then - # 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 - else - DIR="$VS"/Team\ Tools/Dynamic\ Code\ Coverage\ Tools/amd64 - if [[ -d $DIR ]]; then - # This is for MSVC 2019 (on windows-2019). - VSINSTR="$VS"/Team\ Tools/Performance\ Tools/vsinstr.exe - for f in build/cp*/src/*.pyd; do - "$VSINSTR" $f -Verbose -Coverage - done - TOOL="$DIR/CodeCoverage.exe" - cat > extensions.config << EOF - - true - - - .*\\.*\.pyd - - - - EOF - echo "Starting $TOOL in server mode" - "$TOOL" collect \ - -config:extensions.config -session:$SESSION_ID \ - -output:extensions.coverage -verbose & - echo "Started $TOOL" - VS_VER=2019 - fi - fi - echo "##vso[task.setvariable variable=VS_COVERAGE_TOOL]$TOOL" - fi + 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 [[ -n $SESSION_ID ]]; then - if [[ $VS_VER == 2022 ]]; then - "$TOOL" shutdown $SESSION_ID - echo "Coverage collection log" - echo "=======================" - cat extensions.log - else - "$TOOL" shutdown -session:$SESSION_ID - fi + + 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: | - case "$AGENT_OS" in - Linux) - lcov --rc lcov_branch_coverage=1 --capture --directory . \ - --output-file coverage.info - lcov --rc lcov_branch_coverage=1 --output-file coverage.info \ - --extract coverage.info $PWD/src/'*' $PWD/lib/'*' - lcov --rc lcov_branch_coverage=1 --list coverage.info - find . -name '*.gc*' -delete - ;; - Darwin) - 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 - ;; - Windows_NT) - 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 - ;; - *) - exit 1 - ;; - esac + 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: | diff --git a/ci/codespell-ignore-words.txt b/ci/codespell-ignore-words.txt index 5cd80beaa23c..e138f26e216a 100644 --- a/ci/codespell-ignore-words.txt +++ b/ci/codespell-ignore-words.txt @@ -1,4 +1,5 @@ aas +ABD axises coo curvelinear @@ -14,7 +15,9 @@ oint oly te thisy +ure whis wit Copin socio-economic +Ines diff --git a/ci/mypy-stubtest-allowlist.txt b/ci/mypy-stubtest-allowlist.txt index 1d08a690d8f2..12b6feb9b2e0 100644 --- a/ci/mypy-stubtest-allowlist.txt +++ b/ci/mypy-stubtest-allowlist.txt @@ -1,7 +1,7 @@ # Non-typed (and private) modules/functions matplotlib\.backends\..* matplotlib\.tests(\..*)? -matplotlib\.pylab\..* +matplotlib\.pylab(\..*)? matplotlib\._.* matplotlib\.rcsetup\._listify_validator matplotlib\.rcsetup\._validate_linestyle @@ -49,3 +49,6 @@ matplotlib\.figure\.FigureBase\.get_figure # getitem method only exists for 3.10 deprecation backcompatability matplotlib\.inset\.InsetIndicator\.__getitem__ + +# only defined in stubs; not present at runtime +matplotlib\.animation\.EventSourceProtocol diff --git a/ci/schemas/conda-environment.json b/ci/schemas/conda-environment.json index 458676942a44..fb1e821778c3 100644 --- a/ci/schemas/conda-environment.json +++ b/ci/schemas/conda-environment.json @@ -1,6 +1,6 @@ { "title": "conda environment file", - "description": "Support for conda's enviroment.yml files (e.g. `conda env export > environment.yml`)", + "description": "Support for conda's environment.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": { diff --git a/doc/_embedded_plots/figure_subplots_adjust.py b/doc/_embedded_plots/figure_subplots_adjust.py index b4b8d7d32a3d..d32a029fe05d 100644 --- a/doc/_embedded_plots/figure_subplots_adjust.py +++ b/doc/_embedded_plots/figure_subplots_adjust.py @@ -1,28 +1,34 @@ import matplotlib.pyplot as plt -def arrow(p1, p2, **props): - axs[0, 0].annotate( - "", p1, p2, xycoords='figure fraction', - arrowprops=dict(arrowstyle="<->", shrinkA=0, shrinkB=0, **props)) - - 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=[]) -arrow((0, 0.75), (0.1, 0.75)) # left -arrow((0.435, 0.75), (0.565, 0.75)) # wspace -arrow((0.9, 0.75), (1, 0.75)) # right +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.7, "wspace", ha="center") -fig.text(0.95, 0.7, "right", ha="center") +fig.text(0.5, 0.3, "wspace", ha="center") +fig.text(0.05, 0.83, "right", ha="center") -arrow((0.25, 0), (0.25, 0.1)) # bottom -arrow((0.25, 0.435), (0.25, 0.565)) # hspace -arrow((0.25, 0.9), (0.25, 1)) # top -fig.text(0.28, 0.05, "bottom", va="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.28, 0.95, "top", 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/mpl.css b/doc/_static/mpl.css index 9049ddbd8334..25bad17c3938 100644 --- a/doc/_static/mpl.css +++ b/doc/_static/mpl.css @@ -195,15 +195,28 @@ div.wide-table table th.stub { } /* 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{ + .section-toc.toctree-wrapper .toctree-l1>a{ display: none; } -.section-toc.toctree-wrapper li>ul{ - padding-inline-start:0; +.section-toc.toctree-wrapper .toctree-l1>ul{ + padding-left: 0; +} + +.sidebar-cheatsheets { + margin-bottom: 3em; +} + +.sidebar-cheatsheets > h3 { + margin-top: 0; +} + +.sidebar-cheatsheets > img { + width: 100%; } diff --git a/doc/_static/multipage_pdf_thumbnail.svg b/doc/_static/multipage_pdf_thumbnail.svg new file mode 100644 index 000000000000..864c4c647492 --- /dev/null +++ b/doc/_static/multipage_pdf_thumbnail.svg @@ -0,0 +1,12 @@ + + + + + + + + + + Multipage + PDF + diff --git a/doc/_static/switcher.json b/doc/_static/switcher.json index 5a48ec138f4d..36e743db21b8 100644 --- a/doc/_static/switcher.json +++ b/doc/_static/switcher.json @@ -1,15 +1,20 @@ [ { - "name": "3.9 (stable)", - "version": "3.9.2", + "name": "3.10 (stable)", + "version": "3.10.8", "url": "https://matplotlib.org/stable/", "preferred": true }, { - "name": "3.10 (dev)", + "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", 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/14940554.svg b/doc/_static/zenodo_cache/14940554.svg new file mode 100644 index 000000000000..6e7d5c37bf7b --- /dev/null +++ b/doc/_static/zenodo_cache/14940554.svg @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + DOI + + + DOI + + + 10.5281/zenodo.14940554 + + + 10.5281/zenodo.14940554 + + + \ No newline at end of file diff --git a/doc/_static/zenodo_cache/15375714.svg b/doc/_static/zenodo_cache/15375714.svg new file mode 100644 index 000000000000..d5e403138561 --- /dev/null +++ b/doc/_static/zenodo_cache/15375714.svg @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + DOI + + + DOI + + + 10.5281/zenodo.15375714 + + + 10.5281/zenodo.15375714 + + + \ No newline at end of file diff --git a/doc/_static/zenodo_cache/16644850.svg b/doc/_static/zenodo_cache/16644850.svg new file mode 100644 index 000000000000..89910032da4e --- /dev/null +++ b/doc/_static/zenodo_cache/16644850.svg @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + DOI + + + DOI + + + 10.5281/zenodo.16644850 + + + 10.5281/zenodo.16644850 + + + \ No newline at end of file diff --git a/doc/_static/zenodo_cache/16999430.svg b/doc/_static/zenodo_cache/16999430.svg new file mode 100644 index 000000000000..44c448643e91 --- /dev/null +++ b/doc/_static/zenodo_cache/16999430.svg @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + DOI + + + DOI + + + 10.5281/zenodo.16999430 + + + 10.5281/zenodo.16999430 + + + \ No newline at end of file diff --git a/doc/_static/zenodo_cache/17298696.svg b/doc/_static/zenodo_cache/17298696.svg new file mode 100644 index 000000000000..9aa8d7c94349 --- /dev/null +++ b/doc/_static/zenodo_cache/17298696.svg @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + DOI + + + DOI + + + 10.5281/zenodo.17298696 + + + 10.5281/zenodo.17298696 + + + \ No newline at end of file diff --git a/doc/_templates/cheatsheet_sidebar.html b/doc/_templates/cheatsheet_sidebar.html index 3f2b7c4f4db1..2ca6548ddd4d 100644 --- a/doc/_templates/cheatsheet_sidebar.html +++ b/doc/_templates/cheatsheet_sidebar.html @@ -1,6 +1,6 @@ +.. raw:: html + +
+ +.. only:: html + + .. image:: /tutorials/images/thumb/sphx_glr_coding_shortcuts_thumb.png + :alt: Coding shortcuts + + :ref:`sphx_glr_tutorials_coding_shortcuts.py` + +.. raw:: html + +
Coding shortcuts
+
+ + .. raw:: html
@@ -92,6 +109,7 @@ a :ref:`FAQ ` in our :ref:`user guide `. :hidden: /tutorials/pyplot + /tutorials/coding_shortcuts /tutorials/images /tutorials/lifecycle /tutorials/artists diff --git a/galleries/tutorials/lifecycle.py b/galleries/tutorials/lifecycle.py index 4aae4d6c1dbc..4c009f802cf4 100644 --- a/galleries/tutorials/lifecycle.py +++ b/galleries/tutorials/lifecycle.py @@ -169,7 +169,7 @@ ax.barh(group_names, group_data) labels = ax.get_xticklabels() plt.setp(labels, rotation=45, horizontalalignment='right') -ax.set(xlim=[-10000, 140000], xlabel='Total Revenue', ylabel='Company', +ax.set(xlim=(-10000, 140000), xlabel='Total Revenue', ylabel='Company', title='Company Revenue') # %% @@ -187,7 +187,7 @@ ax.barh(group_names, group_data) labels = ax.get_xticklabels() plt.setp(labels, rotation=45, horizontalalignment='right') -ax.set(xlim=[-10000, 140000], xlabel='Total Revenue', ylabel='Company', +ax.set(xlim=(-10000, 140000), xlabel='Total Revenue', ylabel='Company', title='Company Revenue') # %% @@ -220,7 +220,7 @@ def currency(x, pos): labels = ax.get_xticklabels() plt.setp(labels, rotation=45, horizontalalignment='right') -ax.set(xlim=[-10000, 140000], xlabel='Total Revenue', ylabel='Company', +ax.set(xlim=(-10000, 140000), xlabel='Total Revenue', ylabel='Company', title='Company Revenue') ax.xaxis.set_major_formatter(currency) @@ -248,7 +248,7 @@ def currency(x, pos): # Now we move our title up since it's getting a little cramped ax.title.set(y=1.05) -ax.set(xlim=[-10000, 140000], xlabel='Total Revenue', ylabel='Company', +ax.set(xlim=(-10000, 140000), xlabel='Total Revenue', ylabel='Company', title='Company Revenue') ax.xaxis.set_major_formatter(currency) ax.set_xticks([0, 25e3, 50e3, 75e3, 100e3, 125e3]) diff --git a/galleries/users_explain/animations/animations.py b/galleries/users_explain/animations/animations.py index a0669956ab81..45c42f538fa6 100644 --- a/galleries/users_explain/animations/animations.py +++ b/galleries/users_explain/animations/animations.py @@ -60,7 +60,7 @@ # 4) Save or show the animation using one of the following methods: # # - `.pyplot.show` to show the animation in a window -# - `.Animation.to_html5_video` to create a HTML `` -#
-#

Figure caption is here.... -# #

-#
-# - img_block = (f'') - html_block = f'
\n' - html_block += f' \n' - html_block += f' {img_block}\n \n' + if 'style' not in img_attrs: + img_attrs['style'] = f'{style}: {node[style]};' + else: + img_attrs['style'] += f'{style}: {node[style]};' + + #
+ # + # _images/index-1.2x.png + # + #
+ #

Figure caption is here.... + # #

+ #
+ #
+ 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']: - html_block += '
\n' - html_block += f'

{node["caption"]}

\n' - html_block += '
\n' - html_block += '
\n' - self.body.append(html_block) + 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): diff --git a/lib/matplotlib/sphinxext/mathmpl.py b/lib/matplotlib/sphinxext/mathmpl.py index 3e0d562e2d15..30f024524258 100644 --- a/lib/matplotlib/sphinxext/mathmpl.py +++ b/lib/matplotlib/sphinxext/mathmpl.py @@ -146,7 +146,10 @@ def latex2html(node, source): fontset = node['fontset'] fontsize = node['fontsize'] name = 'math-{}'.format( - hashlib.md5(f'{latex}{fontset}{fontsize}'.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) diff --git a/lib/matplotlib/sphinxext/plot_directive.py b/lib/matplotlib/sphinxext/plot_directive.py index 65b25fb913a5..7b46b3145e2b 100644 --- a/lib/matplotlib/sphinxext/plot_directive.py +++ b/lib/matplotlib/sphinxext/plot_directive.py @@ -32,7 +32,7 @@ import matplotlib.pyplot as plt plt.plot([1, 2, 3], [4, 5, 6]) - plt.title("A plotting exammple") + plt.title("A plotting example") 3. Using **doctest** syntax:: @@ -47,6 +47,12 @@ The ``.. plot::`` directive supports the following options: +``:filename-prefix:`` : str + The base name (without the extension) of the outputted image and script + files. The default is to use the same name as the input script, or the + name of the RST document if no script is provided. The filename-prefix for + each plot directive must be unique. + ``:format:`` : {'python', 'doctest'} The format of the input. If unset, the format is auto-detected. @@ -78,6 +84,11 @@ figure. This overwrites the caption given in the content, when the plot is generated from a file. +``:code-caption:`` : str + If specified, the option's argument will be used as a caption for the + code block (when ``:include-source:`` is used). This is added as the + ``:caption:`` option to the ``.. code-block::`` directive. + Additionally, this directive supports all the options of the `image directive `_, except for ``:target:`` (since plot will add its own target). These include @@ -163,8 +174,10 @@ 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*. + """ +from collections import defaultdict import contextlib import doctest from io import StringIO @@ -182,6 +195,7 @@ from docutils.parsers.rst.directives.images import Image import jinja2 # Sphinx dependency. +from sphinx.environment.collectors import EnvironmentCollector from sphinx.errors import ExtensionError import matplotlib @@ -265,12 +279,14 @@ class PlotDirective(Directive): 'scale': directives.nonnegative_int, 'align': Image.align, 'class': directives.class_option, + 'filename-prefix': directives.unchanged, 'include-source': _option_boolean, 'show-source-link': _option_boolean, 'format': _option_format, 'context': _option_context, 'nofigs': directives.flag, 'caption': directives.unchanged, + 'code-caption': directives.unchanged, } def run(self): @@ -312,9 +328,35 @@ def setup(app): app.connect('build-finished', _copy_css_file) metadata = {'parallel_read_safe': True, 'parallel_write_safe': True, 'version': matplotlib.__version__} + app.connect('builder-inited', init_filename_registry) + app.add_env_collector(_FilenameCollector) return metadata +# ----------------------------------------------------------------------------- +# Handle Duplicate Filenames +# ----------------------------------------------------------------------------- + +def init_filename_registry(app): + env = app.builder.env + if not hasattr(env, 'mpl_plot_image_basenames'): + env.mpl_plot_image_basenames = defaultdict(set) + + +class _FilenameCollector(EnvironmentCollector): + def process_doc(self, app, doctree): + pass + + def clear_doc(self, app, env, docname): + if docname in env.mpl_plot_image_basenames: + del env.mpl_plot_image_basenames[docname] + + def merge_other(self, app, env, docnames, other): + for docname in other.mpl_plot_image_basenames: + env.mpl_plot_image_basenames[docname].update( + other.mpl_plot_image_basenames[docname]) + + # ----------------------------------------------------------------------------- # Doctest handling # ----------------------------------------------------------------------------- @@ -600,6 +642,25 @@ def _parse_srcset(entries): return srcset +def check_output_base_name(env, output_base): + docname = env.docname + + if '.' in output_base or '/' in output_base or '\\' in output_base: + raise PlotError( + f"The filename-prefix '{output_base}' is invalid. " + f"It must not contain dots or slashes.") + + for d in env.mpl_plot_image_basenames: + if output_base in env.mpl_plot_image_basenames[d]: + if d == docname: + raise PlotError( + f"The filename-prefix {output_base!r} is used multiple times.") + raise PlotError(f"The filename-prefix {output_base!r} is used multiple" + f"times (it is also used in {env.doc2path(d)}).") + + env.mpl_plot_image_basenames[docname].add(output_base) + + def render_figures(code, code_path, output_dir, output_base, context, function_name, config, context_reset=False, close_figs=False, @@ -722,7 +783,8 @@ def render_figures(code, code_path, output_dir, output_base, context, def run(arguments, content, options, state_machine, state, lineno): document = state_machine.document - config = document.settings.env.config + env = document.settings.env + config = env.config nofigs = 'nofigs' in options if config.plot_srcset and setup.app.builder.name == 'singlehtml': @@ -734,6 +796,7 @@ def run(arguments, content, options, state_machine, state, lineno): options.setdefault('include-source', config.plot_include_source) options.setdefault('show-source-link', config.plot_html_show_source_link) + options.setdefault('filename-prefix', None) if 'class' in options: # classes are parsed into a list of string, and output by simply @@ -775,14 +838,22 @@ def run(arguments, content, options, state_machine, state, lineno): function_name = None code = Path(source_file_name).read_text(encoding='utf-8') - output_base = os.path.basename(source_file_name) + if options['filename-prefix']: + output_base = options['filename-prefix'] + check_output_base_name(env, output_base) + else: + output_base = os.path.basename(source_file_name) else: source_file_name = rst_file code = textwrap.dedent("\n".join(map(str, content))) - counter = document.attributes.get('_plot_counter', 0) + 1 - document.attributes['_plot_counter'] = counter - base, ext = os.path.splitext(os.path.basename(source_file_name)) - output_base = '%s-%d.py' % (base, counter) + if options['filename-prefix']: + output_base = options['filename-prefix'] + check_output_base_name(env, output_base) + else: + base, ext = os.path.splitext(os.path.basename(source_file_name)) + counter = document.attributes.get('_plot_counter', 0) + 1 + document.attributes['_plot_counter'] = counter + output_base = '%s-%d.py' % (base, counter) function_name = None caption = options.get('caption', '') @@ -846,7 +917,7 @@ def run(arguments, content, options, state_machine, state, lineno): # save script (if necessary) if options['show-source-link']: - Path(build_dir, output_base + source_ext).write_text( + Path(build_dir, output_base + (source_ext or '.py')).write_text( doctest.script_from_examples(code) if source_file_name == rst_file and is_doctest else code, @@ -876,7 +947,7 @@ def run(arguments, content, options, state_machine, state, lineno): # Properly indent the caption if caption and config.plot_srcset: - caption = f':caption: {caption}' + caption = ':caption: ' + caption.replace('\n', ' ') elif caption: caption = '\n' + '\n'.join(' ' + line.strip() for line in caption.split('\n')) @@ -887,8 +958,11 @@ def run(arguments, content, options, state_machine, state, lineno): if is_doctest: lines = ['', *code_piece.splitlines()] else: - lines = ['.. code-block:: python', '', - *textwrap.indent(code_piece, ' ').splitlines()] + lines = ['.. code-block:: python'] + if 'code-caption' in options: + code_caption = options['code-caption'].replace('\n', ' ') + lines.append(f' :caption: {code_caption}') + lines.extend(['', *textwrap.indent(code_piece, ' ').splitlines()]) source_code = "\n".join(lines) else: source_code = "" @@ -896,6 +970,9 @@ def run(arguments, content, options, state_machine, state, lineno): if nofigs: images = [] + if 'alt' in options: + options['alt'] = options['alt'].replace('\n', ' ') + opts = [ f':{key}: {val}' for key, val in options.items() if key in ('alt', 'height', 'width', 'scale', 'align', 'class')] @@ -903,7 +980,7 @@ def run(arguments, content, options, state_machine, state, lineno): # 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 + src_name = output_base + (source_ext or '.py') else: src_name = None if config.plot_srcset: diff --git a/lib/matplotlib/sphinxext/roles.py b/lib/matplotlib/sphinxext/roles.py index 301adcd8a5f5..0b696f830543 100644 --- a/lib/matplotlib/sphinxext/roles.py +++ b/lib/matplotlib/sphinxext/roles.py @@ -78,6 +78,22 @@ def _depart_query_reference_node(self, node): self.depart_literal(node) +# We sometimes want to use special notation in rcParam references, e.g. +# to use wildcards. This mapping maps such special notations to real rcParams, +# typically to the first relevant parameter in the group. +_RC_WILDCARD_LINK_MAPPING = { + "animation.[name-of-encoder]_args": "animation.ffmpeg_args", + "figure.subplot.*": "figure.subplot.left", + "figure.subplot.[name]": "figure.subplot.left", + "font.*": "font.family", + "lines.*": "lines.linewidth", + "patch.*": "patch.edgecolor", + "grid.major.*": "grid.major.color", + "grid.minor.*": "grid.minor.color", + "grid.*": "grid.color", +} + + def _rcparam_role(name, rawtext, text, lineno, inliner, options=None, content=None): """ Sphinx role ``:rc:`` to highlight and link ``rcParams`` entries. @@ -89,7 +105,8 @@ def _rcparam_role(name, rawtext, text, lineno, inliner, options=None, content=No # 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' + rc_param_name = _RC_WILDCARD_LINK_MAPPING.get(text, text) + target = f'rcparam_{rc_param_name.replace(".", "_")}' ref_nodes, messages = inliner.interpreted(title, f'{title} <{target}>', 'ref', lineno) @@ -125,6 +142,7 @@ def _mpltype_role(name, rawtext, text, lineno, inliner, options=None, content=No 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}") diff --git a/lib/matplotlib/spines.py b/lib/matplotlib/spines.py index 7e77a393f2a2..741491b3dc58 100644 --- a/lib/matplotlib/spines.py +++ b/lib/matplotlib/spines.py @@ -232,12 +232,13 @@ def _clear(self): """ self._position = None # clear position - def _adjust_location(self): - """Automatically set spine bounds to the view interval.""" - - if self.spine_type == 'circle': - return + def _get_bounds_or_viewLim(self): + """ + Get the bounds of the spine. + If self._bounds is None, return self.axes.viewLim.intervalx + or self.axes.viewLim.intervaly based on self.spine_type + """ if self._bounds is not None: low, high = self._bounds elif self.spine_type in ('left', 'right'): @@ -245,7 +246,16 @@ def _adjust_location(self): elif self.spine_type in ('top', 'bottom'): low, high = self.axes.viewLim.intervalx else: - raise ValueError(f'unknown spine spine_type: {self.spine_type}') + raise ValueError(f'spine_type: {self.spine_type} not supported') + return low, high + + def _adjust_location(self): + """Automatically set spine bounds to the view interval.""" + + if self.spine_type == 'circle': + return + + low, high = self._get_bounds_or_viewLim() if self._patch_type == 'arc': if self.spine_type in ('bottom', 'top'): @@ -265,11 +275,17 @@ def _adjust_location(self): self._path = mpath.Path.arc(np.rad2deg(low), np.rad2deg(high)) if self.spine_type == 'bottom': - rmin, rmax = self.axes.viewLim.intervaly + if self.axis is None: + tr = mtransforms.IdentityTransform() + else: + tr = self.axis.get_transform() + rmin, rmax = tr.transform(self.axes.viewLim.intervaly) try: rorigin = self.axes.get_rorigin() except AttributeError: rorigin = rmin + else: + rorigin = tr.transform(rorigin) scaled_diameter = (rmin - rorigin) / (rmax - rorigin) self._height = scaled_diameter self._width = scaled_diameter @@ -418,7 +434,7 @@ def set_bounds(self, low=None, high=None): 'set_bounds() method incompatible with circular spines') if high is None and np.iterable(low): low, high = low - old_low, old_high = self.get_bounds() or (None, None) + old_low, old_high = self._get_bounds_or_viewLim() if low is None: low = old_low if high is None: diff --git a/lib/matplotlib/stackplot.py b/lib/matplotlib/stackplot.py index 43da57c25da5..25bb2f45a0c4 100644 --- a/lib/matplotlib/stackplot.py +++ b/lib/matplotlib/stackplot.py @@ -6,17 +6,16 @@ (https://stackoverflow.com/users/66549/doug) """ -import itertools import numpy as np -from matplotlib import _api +from matplotlib import cbook, collections, _api, _style_helpers __all__ = ['stackplot'] def stackplot(axes, x, *args, - labels=(), colors=None, hatch=None, baseline='zero', + labels=(), colors=None, baseline='zero', **kwargs): """ Draw a stacked area plot or a streamgraph. @@ -26,11 +25,11 @@ def stackplot(axes, x, *args, x : (N,) array-like y : (M, N) array-like - The data is assumed to be unstacked. Each of the following + The data can be either stacked or unstacked. Each of the following calls is legal:: - stackplot(x, y) # where y has shape (M, N) - stackplot(x, y1, y2, y3) # where y1, y2, y3, y4 have length N + 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: @@ -55,23 +54,26 @@ def stackplot(axes, x, *args, 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`. + All other keyword arguments are passed to `.Axes.fill_between`. The + following parameters additionally accept a sequence of values + corresponding to the *y* datasets: + + - *hatch* + - *edgecolor* + - *facecolor* + - *linewidth* + - *linestyle* + + .. versionadded:: 3.9 + Allowing a sequence of strings for *hatch*. + + .. versionadded:: 3.11 + Allowing sequences of values in above listed `.Axes.fill_between` + parameters. Returns ------- @@ -83,15 +85,13 @@ def stackplot(axes, x, *args, y = np.vstack(args) labels = iter(labels) - if colors is not None: - colors = itertools.cycle(colors) - else: - colors = (axes._get_lines.get_next_color() for _ in y) + if colors is None: + colors = [axes._get_lines.get_next_color() for _ in y] + + kwargs = cbook.normalize_kwargs(kwargs, collections.PolyCollection) + kwargs.setdefault('facecolor', colors) - if hatch is None or isinstance(hatch, str): - hatch = itertools.cycle([hatch]) - else: - hatch = itertools.cycle(hatch) + kwargs, style_gen = _style_helpers.style_generator(kwargs) # Assume data passed has not been 'stacked', so stack it here. # We'll need a float buffer for the upcoming calculations. @@ -130,18 +130,14 @@ def stackplot(axes, x, *args, # Color between x = 0 and the first array. coll = axes.fill_between(x, first_line, stack[0, :], - facecolor=next(colors), - hatch=next(hatch), label=next(labels, None), - **kwargs) + **next(style_gen), **kwargs) coll.sticky_edges.y[:] = [0] r = [coll] # Color between array i-1 and array i for i in range(len(y) - 1): r.append(axes.fill_between(x, stack[i, :], stack[i + 1, :], - facecolor=next(colors), - hatch=next(hatch), label=next(labels, None), - **kwargs)) + **next(style_gen), **kwargs)) return r diff --git a/lib/matplotlib/stackplot.pyi b/lib/matplotlib/stackplot.pyi index 2981d449b566..9509f858a4bf 100644 --- a/lib/matplotlib/stackplot.pyi +++ b/lib/matplotlib/stackplot.pyi @@ -16,3 +16,5 @@ def stackplot( 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 84f99732c709..725fff7b23fd 100644 --- a/lib/matplotlib/streamplot.py +++ b/lib/matplotlib/streamplot.py @@ -6,7 +6,7 @@ import numpy as np import matplotlib as mpl -from matplotlib import _api, cm, patches +from matplotlib import _api, colorizer, patches import matplotlib.colors as mcolors import matplotlib.collections as mcollections import matplotlib.lines as mlines @@ -19,7 +19,8 @@ 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', - broken_streamlines=True): + broken_streamlines=True, *, integration_max_step_scale=1.0, + integration_max_error_scale=1.0, num_arrows=1): """ Draw streamlines of a vector flow. @@ -73,6 +74,29 @@ def streamplot(axes, x, y, u, v, density=1, linewidth=None, color=None, 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 ------- @@ -92,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 @@ -102,8 +141,7 @@ 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 = mpl.rcParams['lines.linewidth'] + linewidth = mpl._val_or_rc(linewidth, 'lines.linewidth') line_kw = {} arrow_kw = dict(arrowstyle=arrowstyle, mutation_scale=10 * arrowsize) @@ -152,7 +190,9 @@ def streamplot(axes, x, y, u, v, density=1, linewidth=None, color=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, broken_streamlines) + t = integrate(xg, yg, broken_streamlines, + integration_max_step_scale, + integration_max_error_scale) if t is not None: trajectories.append(t) else: @@ -180,14 +220,15 @@ def streamplot(axes, x, y, u, v, density=1, linewidth=None, color=None, xg = np.clip(xg, 0, grid.nx - 1) yg = np.clip(yg, 0, grid.ny - 1) - t = integrate(xg, yg, broken_streamlines) + 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()) - cmap = cm._ensure_cmap(cmap) + cmap = colorizer._ensure_cmap(cmap) streamlines = [] arrows = [] @@ -206,25 +247,31 @@ def streamplot(axes, x, y, u, v, density=1, linewidth=None, color=None, points = np.transpose([tx, ty]) streamlines.append(points) - # Add arrows halfway 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) - 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) @@ -467,7 +514,8 @@ def backward_time(xi, yi): dxi, dyi = forward_time(xi, yi) return -dxi, -dyi - def integrate(x0, y0, broken_streamlines=True): + 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. @@ -487,14 +535,18 @@ def integrate(x0, y0, broken_streamlines=True): return None if integration_direction in ['both', 'backward']: s, xyt = _integrate_rk12(x0, y0, dmap, backward_time, maxlength, - broken_streamlines) + broken_streamlines, + integration_max_step_scale, + integration_max_error_scale) stotal += s xy_traj += xyt[::-1] if integration_direction in ['both', 'forward']: dmap.reset_start_point(x0, y0) s, xyt = _integrate_rk12(x0, y0, dmap, forward_time, maxlength, - broken_streamlines) + broken_streamlines, + integration_max_step_scale, + integration_max_error_scale) stotal += s xy_traj += xyt[1:] @@ -511,7 +563,9 @@ class OutOfBounds(IndexError): pass -def _integrate_rk12(x0, y0, dmap, f, maxlength, broken_streamlines=True): +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. @@ -537,7 +591,7 @@ def _integrate_rk12(x0, y0, dmap, f, maxlength, broken_streamlines=True): # 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 @@ -546,6 +600,7 @@ def _integrate_rk12(x0, y0, dmap, f, maxlength, broken_streamlines=True): # 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 diff --git a/lib/matplotlib/streamplot.pyi b/lib/matplotlib/streamplot.pyi index 9da83096e5a8..ca3553edc2fd 100644 --- a/lib/matplotlib/streamplot.pyi +++ b/lib/matplotlib/streamplot.pyi @@ -28,6 +28,10 @@ def streamplot( 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: @@ -80,3 +84,5 @@ class StreamMask: 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 488c6d6ae1ec..80c6de00a18d 100644 --- a/lib/matplotlib/style/__init__.py +++ b/lib/matplotlib/style/__init__.py @@ -1,4 +1,253 @@ -from .core import available, context, library, reload_library, use +""" +Core functions and attributes for the matplotlib style library: +``use`` + Select style sheet to override the current matplotlib settings. +``context`` + Context manager to use a style sheet temporarily. +``available`` + List available style sheets. Underscore-prefixed names are considered private and + not listed, though may still be accessed directly from ``library``. +``library`` + A dictionary of style names and matplotlib settings. +""" -__all__ = ["available", "context", "library", "reload_library", "use"] +import contextlib +import importlib.resources +import logging +import os +from pathlib import Path +import warnings + +import matplotlib as mpl +from matplotlib import _api, _docstring, rc_params_from_file, rcParamsDefault + +_log = logging.getLogger(__name__) + +__all__ = ['use', 'context', 'available', 'library', 'reload_library'] + + +_BASE_LIBRARY_PATH = os.path.join(mpl.get_data_path(), 'stylelib') +# 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' +# A list of rcParams that should not be applied from styles +_STYLE_BLACKLIST = { + 'interactive', 'backend', 'webagg.port', 'webagg.address', + 'webagg.port_retries', 'webagg.open_in_browser', 'backend_fallback', + 'toolbar', 'timezone', 'figure.max_open_warning', + 'figure.raise_window', 'savefig.directory', 'tk.window_focus', + 'docstring.hardcopy', 'date.epoch'} + + +@_docstring.Substitution( + "\n".join(map("- {}".format, sorted(_STYLE_BLACKLIST, key=str.lower))) +) +def use(style): + """ + Use Matplotlib style settings from a style specification. + + The style name of 'default' is reserved for reverting back to + the default style settings. + + .. note:: + + This updates the `.rcParams` with the settings from the style. + `.rcParams` not defined in the style are kept. + + Parameters + ---------- + style : str, dict, Path or list + + A style specification. Valid options are: + + 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 + """ + 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 + + style_alias = {'mpl20': 'default', 'mpl15': 'classic'} + + for style in styles: + 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_from_file(path, use_default_template=False) + 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: + style = rc_params_from_file(style, use_default_template=False) + 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 +def context(style, after_reset=False): + """ + Context manager for using style settings temporarily. + + Parameters + ---------- + style : str, dict, Path or list + A style specification. Valid options are: + + 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; + otherwise, apply style on top of the current settings. + """ + with mpl.rc_context(): + if after_reset: + mpl.rcdefaults() + use(style) + yield + + +def _update_user_library(library): + """Update style library with user-defined rc files.""" + for stylelib_path in map(os.path.expanduser, USER_LIBRARY_PATHS): + styles = _read_style_directory(stylelib_path) + _update_nested_dict(library, styles) + return library + + +@_api.deprecated("3.11") +def update_user_library(library): + return _update_user_library(library) + + +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) + for w in warns: + _log.warning('In %s: %s', path, w.message) + return styles + + +@_api.deprecated("3.11") +def read_style_directory(style_dir): + return _read_style_directory(style_dir) + + +def _update_nested_dict(main_dict, new_dict): + """ + Update nested dict (only level of nesting) with new values. + + Unlike `dict.update`, this assumes that the values of the parent dict are + dicts (or dict-like), so you shouldn't replace the nested dict if it + already exists. Instead you should update the sub-dict. + """ + # update named styles specified by user + for name, rc_dict in new_dict.items(): + main_dict.setdefault(name, {}).update(rc_dict) + return main_dict + + +@_api.deprecated("3.11") +def update_nested_dict(main_dict, new_dict): + return _update_nested_dict(main_dict, new_dict) + + +# Load style library +# ================== +_base_library = _read_style_directory(_BASE_LIBRARY_PATH) +library = {} +available = [] + + +def reload_library(): + """Reload the style library.""" + library.clear() + library.update(_update_user_library(_base_library.copy())) + available[:] = sorted(name for name in library if not name.startswith('_')) + + +reload_library() diff --git a/lib/matplotlib/style/__init__.pyi b/lib/matplotlib/style/__init__.pyi new file mode 100644 index 000000000000..c93b504fe6bd --- /dev/null +++ b/lib/matplotlib/style/__init__.pyi @@ -0,0 +1,20 @@ +from collections.abc import Generator +import contextlib + +from matplotlib import RcParams +from matplotlib.typing import RcStyleType + +USER_LIBRARY_PATHS: list[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/core.py b/lib/matplotlib/style/core.py index e36c3c37a882..c377bc64077a 100644 --- a/lib/matplotlib/style/core.py +++ b/lib/matplotlib/style/core.py @@ -11,227 +11,17 @@ A dictionary of style names and matplotlib settings. """ -import contextlib -import importlib.resources -import logging -import os -from pathlib import Path -import warnings - -import matplotlib as mpl -from matplotlib import _api, _docstring, _rc_params_in_file, rcParamsDefault - -_log = logging.getLogger(__name__) - -__all__ = ['use', 'context', 'available', 'library', 'reload_library'] - - -BASE_LIBRARY_PATH = os.path.join(mpl.get_data_path(), 'stylelib') -# 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' -# A list of rcParams that should not be applied from styles -STYLE_BLACKLIST = { - 'interactive', 'backend', 'webagg.port', 'webagg.address', - 'webagg.port_retries', 'webagg.open_in_browser', 'backend_fallback', - 'toolbar', 'timezone', 'figure.max_open_warning', - 'figure.raise_window', 'savefig.directory', 'tk.window_focus', - 'docstring.hardcopy', 'date.epoch'} - - -@_docstring.Substitution( - "\n".join(map("- {}".format, sorted(STYLE_BLACKLIST, key=str.lower))) +from .. import _api +from . import ( + use, context, available, library, reload_library, USER_LIBRARY_PATHS, + _BASE_LIBRARY_PATH as BASE_LIBRARY_PATH, + _STYLE_EXTENSION as STYLE_EXTENSION, + _STYLE_BLACKLIST as STYLE_BLACKLIST, ) -def use(style): - """ - Use Matplotlib style settings from a style specification. - - The style name of 'default' is reserved for reverting back to - the default style settings. - - .. note:: - - This updates the `.rcParams` with the settings from the style. - `.rcParams` not defined in the style are kept. - - Parameters - ---------- - style : str, dict, Path or list - - A style specification. Valid options are: - - 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 - """ - 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 - - style_alias = {'mpl20': 'default', 'mpl15': 'classic'} - - for style in styles: - 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: - 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 -def context(style, after_reset=False): - """ - Context manager for using style settings temporarily. - - Parameters - ---------- - style : str, dict, Path or list - A style specification. Valid options are: - - 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; - otherwise, apply style on top of the current settings. - """ - with mpl.rc_context(): - if after_reset: - mpl.rcdefaults() - use(style) - yield - - -def update_user_library(library): - """Update style library with user-defined rc files.""" - for stylelib_path in map(os.path.expanduser, USER_LIBRARY_PATHS): - styles = read_style_directory(stylelib_path) - update_nested_dict(library, styles) - return library - - -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_in_file(path) - for w in warns: - _log.warning('In %s: %s', path, w.message) - return styles - - -def update_nested_dict(main_dict, new_dict): - """ - Update nested dict (only level of nesting) with new values. - - Unlike `dict.update`, this assumes that the values of the parent dict are - dicts (or dict-like), so you shouldn't replace the nested dict if it - already exists. Instead you should update the sub-dict. - """ - # update named styles specified by user - for name, rc_dict in new_dict.items(): - main_dict.setdefault(name, {}).update(rc_dict) - return main_dict - - -# Load style library -# ================== -_base_library = read_style_directory(BASE_LIBRARY_PATH) -library = {} -available = [] - - -def reload_library(): - """Reload the style library.""" - library.clear() - library.update(update_user_library(_base_library)) - available[:] = sorted(library.keys()) +__all__ = [ + "use", "context", "available", "library", "reload_library", + "USER_LIBRARY_PATHS", "BASE_LIBRARY_PATH", "STYLE_EXTENSION", "STYLE_BLACKLIST", +] -reload_library() +_api.warn_deprecated("3.11", name=__name__, obj_type="module") diff --git a/lib/matplotlib/style/core.pyi b/lib/matplotlib/style/core.pyi index 73400492143c..ee21d2f41ef5 100644 --- a/lib/matplotlib/style/core.pyi +++ b/lib/matplotlib/style/core.pyi @@ -5,7 +5,9 @@ from matplotlib import RcParams from matplotlib.typing import RcStyleType USER_LIBRARY_PATHS: list[str] = ... +BASE_LIBRARY_PATH: str = ... STYLE_EXTENSION: str = ... +STYLE_BLACKLIST: set[str] = ... def use(style: RcStyleType) -> None: ... @contextlib.contextmanager @@ -17,3 +19,8 @@ library: dict[str, RcParams] available: list[str] def reload_library() -> None: ... + +__all__ = [ + "use", "context", "available", "library", "reload_library", + "USER_LIBRARY_PATHS", "BASE_LIBRARY_PATH", "STYLE_EXTENSION", "STYLE_BLACKLIST", +] diff --git a/lib/matplotlib/style/meson.build b/lib/matplotlib/style/meson.build index 03e7972132bb..e7a183c8581c 100644 --- a/lib/matplotlib/style/meson.build +++ b/lib/matplotlib/style/meson.build @@ -4,6 +4,7 @@ python_sources = [ ] typing_sources = [ + '__init__.pyi', 'core.pyi', ] diff --git a/lib/matplotlib/table.py b/lib/matplotlib/table.py index 212cd9f45187..91dddba6c31f 100644 --- a/lib/matplotlib/table.py +++ b/lib/matplotlib/table.py @@ -33,6 +33,8 @@ from .transforms import Bbox from .path import Path +from .cbook import _is_pandas_dataframe + class Cell(Rectangle): """ @@ -238,6 +240,13 @@ class Table(Artist): """ A table of cells. + .. note:: + + ``table()`` has some fundamental design limitations and will not be + developed further. If you need more functionality, consider + `blume `__. + + The table consists of a grid of cells, which are indexed by (row, column). For a simple table, you'll have a full grid of cells with indices from @@ -656,6 +665,12 @@ def table(ax, """ Add a table to an `~.axes.Axes`. + .. note:: + + ``table()`` has some fundamental design limitations and will not be + developed further. If you need more functionality, consider + `blume `__. + At least one of *cellText* or *cellColours* must be specified. These parameters must be 2D lists, in which the outer lists define the rows and the inner list define the column values per row. Each row must have the @@ -670,7 +685,7 @@ def table(ax, 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 @@ -740,6 +755,21 @@ 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: @@ -821,5 +851,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 index 0108ecd99f89..167d98d3c4cb 100644 --- a/lib/matplotlib/table.pyi +++ b/lib/matplotlib/table.pyi @@ -10,6 +10,8 @@ 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__( @@ -68,7 +70,7 @@ class Table(Artist): def table( ax: Axes, - cellText: Sequence[Sequence[str]] | None = ..., + cellText: Sequence[Sequence[str]] | DataFrame | None = ..., cellColours: Sequence[Sequence[ColorType]] | None = ..., cellLoc: Literal["left", "center", "right"] = ..., colWidths: Sequence[float] | None = ..., diff --git a/lib/matplotlib/testing/__init__.py b/lib/matplotlib/testing/__init__.py index 19113d399626..eae1bfefa211 100644 --- a/lib/matplotlib/testing/__init__.py +++ b/lib/matplotlib/testing/__init__.py @@ -1,13 +1,15 @@ """ Helper functions for testing. """ -from pathlib import Path -from tempfile import TemporaryDirectory +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 _api @@ -52,7 +54,7 @@ def setup(): def subprocess_run_for_testing(command, env=None, timeout=60, stdout=None, stderr=None, check=False, text=True, - capture_output=False): + capture_output=False, **kwargs): """ Create and run a subprocess. @@ -85,17 +87,29 @@ def subprocess_run_for_testing(command, env=None, timeout=60, stdout=None, Raises ------ + pytest.skip + If running on emscripten, which does not support subprocesses. pytest.xfail If platform is Cygwin and subprocess reports a fork() failure. """ + if sys.platform == 'emscripten': + import pytest + pytest.skip('emscripten does not support subprocesses') if capture_output: stdout = stderr = subprocess.PIPE + # Add CREATE_NO_WINDOW flag on Windows to prevent console window overhead + # This is added in an attempt to fix flaky timeouts of subprocesses on Windows + if sys.platform == 'win32': + if 'creationflags' not in kwargs: + kwargs['creationflags'] = subprocess.CREATE_NO_WINDOW + else: + kwargs['creationflags'] |= subprocess.CREATE_NO_WINDOW try: proc = subprocess.run( command, env=env, timeout=timeout, check=check, stdout=stdout, stderr=stderr, - text=text + text=text, **kwargs ) except BlockingIOError: if sys.platform == "cygwin": @@ -103,6 +117,16 @@ def subprocess_run_for_testing(command, env=None, timeout=60, stdout=None, import pytest pytest.xfail("Fork failure") raise + except subprocess.CalledProcessError as e: + if e.stdout: + _log.error(f"Subprocess output:\n{e.stdout}") + if e.stderr: + _log.error(f"Subprocess error:\n{e.stderr}") + raise e + if proc.stdout: + _log.debug(f"Subprocess output:\n{proc.stdout}") + if proc.stderr: + _log.debug(f"Subprocess error:\n{proc.stderr}") return proc @@ -132,13 +156,26 @@ def subprocess_run_helper(func, *args, timeout, extra_env=None): f"_module = importlib.util.module_from_spec(_spec);" f"_spec.loader.exec_module(_module);" f"_module.{target}()", - *args + *args, ], - env={**os.environ, "SOURCE_DATE_EPOCH": "0", **(extra_env or {})}, - timeout=timeout, check=True, + env={ + **os.environ, + "SOURCE_DATE_EPOCH": "0", + # subprocess_run_helper sets SOURCE_DATE_EPOCH=0 above, so for a dirty tree, + # the version will have the date 19700101 which breaks pickle tests with a + # warning if the working tree is dirty. + # + # This will also avoid at least one additional subprocess call for + # setuptools-scm query git, so we tell the subprocess what version + # to report as the test process. + "SETUPTOOLS_SCM_PRETEND_VERSION_FOR_MATPLOTLIB": mpl.__version__, + **(extra_env or {}), + }, + timeout=timeout, + check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, - text=True + text=True, ) return proc @@ -164,7 +201,8 @@ def _check_for_pgf(texsystem): """, encoding="utf-8") try: subprocess.check_call( - [texsystem, "-halt-on-error", str(tex_path)], cwd=tmpdir, + [texsystem, "-halt-on-error", "-no-shell-escape", + str(tex_path)], cwd=tmpdir, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) except (OSError, subprocess.CalledProcessError): return False @@ -175,7 +213,7 @@ def _has_tex_package(package): try: mpl.dviread.find_tex_file(f"{package}.sty") return True - except FileNotFoundError: + except (FileNotFoundError, OSError): return False @@ -232,3 +270,44 @@ def is_ci_environment(): 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 index 6917b6a5a380..accf973615fa 100644 --- a/lib/matplotlib/testing/__init__.pyi +++ b/lib/matplotlib/testing/__init__.pyi @@ -16,6 +16,7 @@ def subprocess_run_for_testing( *, text: Literal[True], capture_output: bool = ..., + **kwargs, ) -> subprocess.CompletedProcess[str]: ... @overload def subprocess_run_for_testing( @@ -27,6 +28,7 @@ def subprocess_run_for_testing( check: bool = ..., text: Literal[False] = ..., capture_output: bool = ..., + **kwargs, ) -> subprocess.CompletedProcess[bytes]: ... @overload def subprocess_run_for_testing( @@ -38,6 +40,7 @@ def subprocess_run_for_testing( check: bool = ..., text: bool = ..., capture_output: bool = ..., + **kwargs, ) -> subprocess.CompletedProcess[bytes] | subprocess.CompletedProcess[str]: ... def subprocess_run_helper( func: Callable[[], None], @@ -52,3 +55,4 @@ def ipython_in_subprocess( 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/compare.py b/lib/matplotlib/testing/compare.py index 0f252bc1da8e..2b94847a72b6 100644 --- a/lib/matplotlib/testing/compare.py +++ b/lib/matplotlib/testing/compare.py @@ -19,7 +19,7 @@ from PIL import Image import matplotlib as mpl -from matplotlib import cbook +from matplotlib import cbook, _image from matplotlib.testing.exceptions import ImageComparisonFailure _log = logging.getLogger(__name__) @@ -46,22 +46,20 @@ def get_cache_dir(): 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')) + sha256.update(str(mpl._get_executable_info("inkscape").version).encode('utf-8')) - return md5.hexdigest() + return sha256.hexdigest() class _ConverterError(Exception): @@ -99,6 +97,16 @@ def _read_until(self, 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): def __call__(self, orig, dest): if not self._proc: @@ -216,7 +224,7 @@ def __del__(self): class _SVGWithMatplotlibFontsConverter(_SVGConverter): """ - A SVG converter which explicitly adds the fonts shipped by Matplotlib to + An 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). """ @@ -230,6 +238,12 @@ def __call__(self, 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: @@ -300,7 +314,7 @@ def convert(filename, cache): _log.debug("For %s: converting to png.", filename) convert = converter[path.suffix[1:]] if path.suffix == ".svg": - contents = path.read_text() + 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 @@ -397,8 +411,8 @@ 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 - with the `.calculate_rms` function. + PNG via the `!converter` dictionary. The underlying RMS is calculated + in a similar way to the `.calculate_rms` function. Parameters ---------- @@ -469,17 +483,12 @@ def compare_images(expected, actual, tol, in_decorator=False): if np.array_equal(expected_image, actual_image): return None - # convert to signed integers, so that the images can be subtracted without - # overflow - expected_image = expected_image.astype(np.int16) - actual_image = actual_image.astype(np.int16) - - rms = calculate_rms(expected_image, actual_image) + rms, abs_diff = _image.calculate_rms_and_diff(expected_image, actual_image) if rms <= tol: return None - save_diff_image(expected, actual, diff_image) + Image.fromarray(abs_diff).save(diff_image, format="png") results = dict(rms=rms, expected=str(expected), actual=str(actual), diff=str(diff_image), tol=tol) diff --git a/lib/matplotlib/testing/conftest.py b/lib/matplotlib/testing/conftest.py index 3f96de611195..6f87d9826cc3 100644 --- a/lib/matplotlib/testing/conftest.py +++ b/lib/matplotlib/testing/conftest.py @@ -1,5 +1,8 @@ -import pytest +import os import sys + +import pytest + import matplotlib from matplotlib import _api @@ -80,6 +83,13 @@ def mpl_test_settings(request): matplotlib.use(prev_backend) +@pytest.fixture +def high_memory(pytestconfig): + from matplotlib.testing import is_ci_environment + if not (os.environ.get('MPL_TEST_EXPENSIVE') or is_ci_environment()): + pytest.skip('Test uses too much memory') + + @pytest.fixture def pd(): """ @@ -125,3 +135,54 @@ def test_imshow_xarray(xr): 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 index 2af0eb93cc8a..df9f3bdeb36f 100644 --- a/lib/matplotlib/testing/conftest.pyi +++ b/lib/matplotlib/testing/conftest.pyi @@ -7,6 +7,10 @@ def pytest_unconfigure(config: pytest.Config) -> None: ... @pytest.fixture def mpl_test_settings(request: pytest.FixtureRequest) -> None: ... @pytest.fixture +def high_memory(pytestconfig: pytest.Config) -> 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 6f1af7debdb3..f404d7ae84ee 100644 --- a/lib/matplotlib/testing/decorators.py +++ b/lib/matplotlib/testing/decorators.py @@ -15,6 +15,7 @@ import matplotlib.units import matplotlib.testing from matplotlib import _pylab_helpers, cbook, ft2font, pyplot as plt, ticker +from matplotlib.figure import Figure from .compare import comparable_formats, compare_images, make_test_filename from .exceptions import ImageComparisonFailure @@ -138,6 +139,8 @@ def copy_baseline(self, baseline, extension): try: if 'microsoft' in uname().release.lower(): raise OSError # On WSL, symlink breaks silently + if sys.platform == 'emscripten': + raise OSError os.symlink(orig_expected_path, expected_fname) except OSError: # On Windows, symlink *may* be unavailable. shutil.copyfile(orig_expected_path, expected_fname) @@ -204,6 +207,7 @@ def wrapper(*args, extension, request, **kwargs): 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', @@ -279,7 +283,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 @@ -346,7 +350,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. @@ -359,8 +363,13 @@ 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. @@ -404,27 +413,21 @@ def wrapper(*args, ext, request, **kwargs): file_name = "".join(c for c in request.node.name if c in ALLOWED_CHARS) - try: - fig_test = plt.figure("test") - fig_ref = plt.figure("reference") - 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) - fig_ref.savefig(ref_image_path) - _raise_on_image_difference( - ref_image_path, test_image_path, tol=tol - ) - finally: - plt.close(fig_test) - plt.close(fig_ref) + fig_test = Figure() + fig_ref = Figure() + func(*args, fig_test=fig_test, fig_ref=fig_ref, **kwargs) + if len(fig_test.get_children()) == 1 and len(fig_ref.get_children()) == 1: + # no artists have been added. The only child is fig.patch. + raise RuntimeError("Both figures are empty. Make sure you are " + "plotting to fig_test or fig_ref.") + + test_image_path = result_dir / (file_name + "." + ext) + ref_image_path = result_dir / (file_name + "-expected." + ext) + fig_test.savefig(test_image_path) + fig_ref.savefig(ref_image_path) + _raise_on_image_difference( + ref_image_path, test_image_path, tol=tol + ) parameters = [ param diff --git a/lib/matplotlib/testing/jpl_units/UnitDbl.py b/lib/matplotlib/testing/jpl_units/UnitDbl.py index 5226c06ad54b..95869740ace2 100644 --- a/lib/matplotlib/testing/jpl_units/UnitDbl.py +++ b/lib/matplotlib/testing/jpl_units/UnitDbl.py @@ -49,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 = _api.check_getitem(self.allowed, units=units) + data = _api.getitem_checked(self.allowed, units=units) self._value = float(value * data[0]) self._units = data[1] @@ -69,7 +69,7 @@ def convert(self, units): """ if self._units == units: return self._value - data = _api.check_getitem(self.allowed, units=units) + data = _api.getitem_checked(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" diff --git a/lib/matplotlib/testing/widgets.py b/lib/matplotlib/testing/widgets.py index 3962567aa7c0..c528ffb2537c 100644 --- a/lib/matplotlib/testing/widgets.py +++ b/lib/matplotlib/testing/widgets.py @@ -8,6 +8,8 @@ from unittest import mock +from matplotlib import _api +from matplotlib.backend_bases import MouseEvent, KeyEvent import matplotlib.pyplot as plt @@ -24,6 +26,7 @@ def noop(*args, **kwargs): pass +@_api.deprecated("3.11", alternative="MouseEvent or KeyEvent") 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. @@ -65,6 +68,7 @@ def mock_event(ax, button=1, xdata=0, ydata=0, key=None, step=1): return event +@_api.deprecated("3.11", alternative="callbacks.process(event)") def do_event(tool, etype, button=1, xdata=0, ydata=0, key=None, step=1): """ Trigger an event on the given tool. @@ -105,15 +109,12 @@ def click_and_drag(tool, start, end, key=None): 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) + ax = tool.ax + if key is not None: # Press key + KeyEvent._from_ax_coords("key_press_event", ax, start, key)._process() # 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) + MouseEvent._from_ax_coords("button_press_event", ax, start, 1)._process() + MouseEvent._from_ax_coords("motion_notify_event", ax, end, 1)._process() + MouseEvent._from_ax_coords("button_release_event", ax, end, 1)._process() + if key is not None: # Release key + KeyEvent._from_ax_coords("key_release_event", ax, end, key)._process() diff --git a/lib/matplotlib/tests/baseline_images/dviread/lualatex.json b/lib/matplotlib/tests/baseline_images/dviread/lualatex.json new file mode 100644 index 000000000000..8f2d95017ec7 --- /dev/null +++ b/lib/matplotlib/tests/baseline_images/dviread/lualatex.json @@ -0,0 +1 @@ +[{"text": [[5046272, 4128768, "A", "lmroman10-regular.otf", 9.96, {}], [5756027, 4128768, "L", "lmroman10-regular.otf", 9.96, {}], [5929697, 4012179, "A", "lmroman7-regular.otf", 6.97, {}], [6218125, 4128768, "T", "lmroman10-regular.otf", 9.96, {}], [6582045, 4269998, "E", "lmroman10-regular.otf", 9.96, {}], [6946425, 4128768, "X", "lmroman10-regular.otf", 9.96, {}], [7656180, 4128768, "d", "DejaVuSans.ttf", 9.96, {"extend": 1.25, "slant": 0.25, "embolden": 0.25}], [8072180, 4128768, "o", "DejaVuSans.ttf", 9.96, {"extend": 1.25, "slant": 0.25, "embolden": 0.25}], [8473140, 4128768, "c", "DejaVuSans.ttf", 9.96, {"extend": 1.25, "slant": 0.25, "embolden": 0.25}], [8833460, 4128768, ".", "DejaVuSans.ttf", 9.96, {"extend": 1.25, "slant": 0.25, "embolden": 0.25}]], "boxes": []}, {"text": [[13686374, 5056284, "\u03c0", "cmmi5.pfb", 4.98, {}], [13716923, 5390308, "2", "cmr5.pfb", 4.98, {}], [13355110, 5463127, "integraldisplay", "cmex10.pfb", 9.96, {}], [13406537, 7324364, "0", "cmr7.pfb", 6.97, {}], [14010471, 5627696, "parenleftBig", "cmex10.pfb", 9.96, {}], [14937513, 5911796, "x", "cmmi10.pfb", 9.96, {}], [14480510, 6804696, "s", "lmroman10-regular.otf", 9.96, {}], [14738721, 6804696, "i", "lmroman10-regular.otf", 9.96, {}], [14920911, 6804696, "n", "lmroman10-regular.otf", 9.96, {}], [15394516, 6804696, "x", "cmmi10.pfb", 9.96, {}], [15847715, 5627696, "parenrightBig", "cmex10.pfb", 9.96, {}], [16239111, 5763501, "2", "cmr7.pfb", 6.97, {}], [16642338, 6355152, "d", "lmroman10-regular.otf", 9.96, {}], [17006718, 6355152, "x", "cmmi10.pfb", 9.96, {}]], "boxes": [[13686374, 5130818, 26213, 284106], [14480510, 6204418, 26213, 1288562]]}] diff --git a/lib/matplotlib/tests/baseline_images/dviread/pdflatex.json b/lib/matplotlib/tests/baseline_images/dviread/pdflatex.json new file mode 100644 index 000000000000..4754b722aa58 --- /dev/null +++ b/lib/matplotlib/tests/baseline_images/dviread/pdflatex.json @@ -0,0 +1 @@ +[{"text": [[5046272, 4128768, "A", "cmr10.pfb", 9.96, {}], [5756246, 4128768, "L", "cmr10.pfb", 9.96, {}], [5929917, 3994421, "A", "cmr7.pfb", 6.97, {}], [6218464, 4128768, "T", "cmr10.pfb", 9.96, {}], [6582530, 4269852, "E", "cmr10.pfb", 9.96, {}], [6946620, 4128768, "X", "cmr10.pfb", 9.96, {}], [7656594, 4128768, "d", "cmr10.pfb", 9.96, {}], [8020684, 4128768, "o", "cmr10.pfb", 9.96, {}], [8366570, 4128768, "c", "cmr10.pfb", 9.96, {}], [8657841, 4128768, ".", "cmr10.pfb", 9.96, {}]], "boxes": []}, {"text": [[13686591, 5056284, "\u03c0", "cmmi5.pfb", 4.98, {}], [13717140, 5390308, "2", "cmr5.pfb", 4.98, {}], [13355327, 5463127, "integraldisplay", "cmex10.pfb", 9.96, {}], [13406754, 7324364, "0", "cmr7.pfb", 6.97, {}], [14010688, 5627696, "parenleftBig", "cmex10.pfb", 9.96, {}], [14937658, 5911796, "x", "cmmi10.pfb", 9.96, {}], [14480727, 6804696, "s", "cmr10.pfb", 9.96, {}], [14739230, 6804696, "i", "cmr10.pfb", 9.96, {}], [14921275, 6804696, "n", "cmr10.pfb", 9.96, {}], [15394589, 6804696, "x", "cmmi10.pfb", 9.96, {}], [15847788, 5627696, "parenrightBig", "cmex10.pfb", 9.96, {}], [16239184, 5763501, "2", "cmr7.pfb", 6.97, {}], [16642411, 6355152, "d", "cmr10.pfb", 9.96, {}], [17006501, 6355152, "x", "cmmi10.pfb", 9.96, {}]], "boxes": [[13686591, 5130818, 26213, 284106], [14480727, 6204418, 26213, 1288418]]}] diff --git a/lib/matplotlib/tests/baseline_images/dviread/test.dvi b/lib/matplotlib/tests/baseline_images/dviread/test.dvi deleted file mode 100644 index 93751ffdcba0..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/dviread/test.dvi and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/dviread/test.json b/lib/matplotlib/tests/baseline_images/dviread/test.json deleted file mode 100644 index 0809cb9531f1..000000000000 --- a/lib/matplotlib/tests/baseline_images/dviread/test.json +++ /dev/null @@ -1,94 +0,0 @@ -[ - { - "text": [ - [5046272, 4128768, "T", "cmr10", 9.96], - [5519588, 4128768, "h", "cmr10", 9.96], - [5883678, 4128768, "i", "cmr10", 9.96], - [6065723, 4128768, "s", "cmr10", 9.96], - [6542679, 4128768, "i", "cmr10", 9.96], - [6724724, 4128768, "s", "cmr10", 9.96], - [7201680, 4128768, "a", "cmr10", 9.96], - [7747814, 4128768, "L", "cmr10", 9.96], - [7921485, 3994421, "A", "cmr7", 6.97], - [8210032, 4128768, "T", "cmr10", 9.96], - [8574098, 4269852, "E", "cmr10", 9.96], - [8938188, 4128768, "X", "cmr10", 9.96], - [9648162, 4128768, "t", "cmr10", 9.96], - [9903025, 4128768, "e", "cmr10", 9.96], - [10194296, 4128768, "s", "cmr10", 9.96], - [10452799, 4128768, "t", "cmr10", 9.96], - [10926115, 4128768, "d", "cmr10", 9.96], - [11290205, 4128768, "o", "cmr10", 9.96], - [11636091, 4128768, "c", "cmr10", 9.96], - [11927362, 4128768, "u", "cmr10", 9.96], - [12291452, 4128768, "m", "cmr10", 9.96], - [12837587, 4128768, "e", "cmr10", 9.96], - [13128858, 4128768, "n", "cmr10", 9.96], - [13474743, 4128768, "t", "cmr10", 9.96], - [4063232, 4915200, "f", "cmr10", 9.96], - [4263482, 4915200, "o", "cmr10", 9.96], - [4591163, 4915200, "r", "cmr10", 9.96], - [5066299, 4915200, "t", "cmr10", 9.96], - [5321162, 4915200, "e", "cmr10", 9.96], - [5612433, 4915200, "s", "cmr10", 9.96], - [5870936, 4915200, "t", "cmr10", 9.96], - [6125799, 4915200, "i", "cmr10", 9.96], - [6307844, 4915200, "n", "cmr10", 9.96], - [6671934, 4915200, "g", "cmr10", 9.96], - [7218068, 4915200, "m", "cmr10", 9.96], - [7764203, 4915200, "a", "cmr10", 9.96], - [8091884, 4915200, "t", "cmr10", 9.96], - [8346747, 4915200, "p", "cmr10", 9.96], - [8710837, 4915200, "l", "cmr10", 9.96], - [8892882, 4915200, "o", "cmr10", 9.96], - [9220563, 4915200, "t", "cmr10", 9.96], - [9475426, 4915200, "l", "cmr10", 9.96], - [9657471, 4915200, "i", "cmr10", 9.96], - [9839516, 4915200, "b", "cmr10", 9.96], - [10203606, 4915200, "'", "cmr10", 9.96], - [10385651, 4915200, "s", "cmr10", 9.96], - [10862607, 4915200, "d", "cmr10", 9.96], - [11226697, 4915200, "v", "cmr10", 9.96], - [11572583, 4915200, "i", "cmr10", 9.96], - [11754628, 4915200, "r", "cmr10", 9.96], - [12011311, 4915200, "e", "cmr10", 9.96], - [12302582, 4915200, "a", "cmr10", 9.96], - [12630263, 4915200, "d", "cmr10", 9.96], - [13686591, 6629148, "\u0019", "cmmi5", 4.98], - [13717140, 6963172, "2", "cmr5", 4.98], - [13355327, 7035991, "Z", "cmex10", 9.96], - [13406754, 8897228, "0", "cmr7", 6.97], - [14010688, 7200560, "\u0010", "cmex10", 9.96], - [14937658, 7484660, "x", "cmmi10", 9.96], - [14480727, 8377560, "s", "cmr10", 9.96], - [14739230, 8377560, "i", "cmr10", 9.96], - [14921275, 8377560, "n", "cmr10", 9.96], - [15394589, 8377560, "x", "cmmi10", 9.96], - [15847788, 7200560, "\u0011", "cmex10", 9.96], - [16239184, 7336365, "2", "cmr7", 6.97], - [16642411, 7928016, "d", "cmr10", 9.96], - [17006501, 7928016, "x", "cmmi10", 9.96] - ], - "boxes": [ - [4063232, 5701632, 65536, 22609920], - [13686591, 6703682, 26213, 284106], - [14480727, 7777282, 26213, 1288418] - ] - }, - { - "text": [ - [5046272, 4128768, "a", "cmr10", 9.96], - [5373953, 4128768, "n", "cmr10", 9.96], - [5738043, 4128768, "o", "cmr10", 9.96], - [6065724, 4128768, "t", "cmr10", 9.96], - [6320587, 4128768, "h", "cmr10", 9.96], - [6684677, 4128768, "e", "cmr10", 9.96], - [6975948, 4128768, "r", "cmr10", 9.96], - [7451084, 4128768, "p", "cmr10", 9.96], - [7815174, 4128768, "a", "cmr10", 9.96], - [8142855, 4128768, "g", "cmr10", 9.96], - [8470536, 4128768, "e", "cmr10", 9.96] - ], - "boxes": [] - } -] diff --git a/lib/matplotlib/tests/baseline_images/dviread/test.tex b/lib/matplotlib/tests/baseline_images/dviread/test.tex index 33220fedae3e..4a2d4720c065 100644 --- a/lib/matplotlib/tests/baseline_images/dviread/test.tex +++ b/lib/matplotlib/tests/baseline_images/dviread/test.tex @@ -1,17 +1,19 @@ -% source file for test.dvi \documentclass{article} +\usepackage{iftex} +\iftutex\usepackage{fontspec}\fi % xetex or luatex \pagestyle{empty} + \begin{document} -This is a \LaTeX\ test document\\ -for testing matplotlib's dviread +A \LaTeX { + \iftutex\fontspec{DejaVuSans.ttf}[ + FakeSlant=0.25, FakeStretch=1.25, FakeBold=2.5, Color=0000FF]\fi + doc. +} -\noindent\rule{\textwidth}{1pt} +\newpage \[ \int\limits_0^{\frac{\pi}{2}} \Bigl(\frac{x}{\sin x}\Bigr)^2\,\mathrm{d}x \] \special{Special!} -\newpage -another page - \end{document} diff --git a/lib/matplotlib/tests/baseline_images/dviread/xelatex.json b/lib/matplotlib/tests/baseline_images/dviread/xelatex.json new file mode 100644 index 000000000000..8fb81ddf0c7e --- /dev/null +++ b/lib/matplotlib/tests/baseline_images/dviread/xelatex.json @@ -0,0 +1 @@ +[{"text": [[5046272, 4128768, "A", "lmroman10-regular.otf", 9.96, {}], [5756027, 4128768, "L", "lmroman10-regular.otf", 9.96, {}], [5929697, 4012179, "A", "lmroman7-regular.otf", 6.97, {}], [6218125, 4128768, "T", "lmroman10-regular.otf", 9.96, {}], [6582045, 4269998, "E", "lmroman10-regular.otf", 9.96, {}], [6946425, 4128768, "X", "lmroman10-regular.otf", 9.96, {}], [7656180, 4128768, "d", "DejaVuSans.ttf", 9.96, {"rgba": [0, 0, 255, 255], "extend": 1.25, "slant": 0.25, "embolden": 0.25}], [8176180, 4128768, "o", "DejaVuSans.ttf", 9.96, {"rgba": [0, 0, 255, 255], "extend": 1.25, "slant": 0.25, "embolden": 0.25}], [8677380, 4128768, "c", "DejaVuSans.ttf", 9.96, {"rgba": [0, 0, 255, 255], "extend": 1.25, "slant": 0.25, "embolden": 0.25}], [9127780, 4128768, ".", "DejaVuSans.ttf", 9.96, {"rgba": [0, 0, 255, 255], "extend": 1.25, "slant": 0.25, "embolden": 0.25}]], "boxes": []}, {"text": [[13686374, 5056284, "\u03c0", "cmmi5.pfb", 4.98, {}], [13716923, 5390308, "2", "cmr5.pfb", 4.98, {}], [13355110, 5463127, "integraldisplay", "cmex10.pfb", 9.96, {}], [13406537, 7324364, "0", "cmr7.pfb", 6.97, {}], [14010471, 5627696, "parenleftBig", "cmex10.pfb", 9.96, {}], [14937513, 5911796, "x", "cmmi10.pfb", 9.96, {}], [14480510, 6804696, "s", "lmroman10-regular.otf", 9.96, {}], [14738722, 6804696, "i", "lmroman10-regular.otf", 9.96, {}], [14920912, 6804696, "n", "lmroman10-regular.otf", 9.96, {}], [15394516, 6804696, "x", "cmmi10.pfb", 9.96, {}], [15847715, 5627696, "parenrightBig", "cmex10.pfb", 9.96, {}], [16239111, 5763501, "2", "cmr7.pfb", 6.97, {}], [16642338, 6355152, "d", "lmroman10-regular.otf", 9.96, {}], [17006718, 6355152, "x", "cmmi10.pfb", 9.96, {}]], "boxes": [[13686374, 5130818, 26213, 284106], [14480510, 6204418, 26213, 1288562]]}] diff --git a/lib/matplotlib/tests/baseline_images/test_agg_filter/agg_filter_alpha.gif b/lib/matplotlib/tests/baseline_images/test_agg_filter/agg_filter_alpha.gif new file mode 100644 index 000000000000..01a2d0bc288e Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_agg_filter/agg_filter_alpha.gif differ diff --git a/lib/matplotlib/tests/baseline_images/test_arrow_patches/fancyarrow_test_image.pdf b/lib/matplotlib/tests/baseline_images/test_arrow_patches/fancyarrow_test_image.pdf deleted file mode 100644 index 124014fc4869..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_arrow_patches/fancyarrow_test_image.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_arrow_patches/fancyarrow_test_image.svg b/lib/matplotlib/tests/baseline_images/test_arrow_patches/fancyarrow_test_image.svg deleted file mode 100644 index 6fb9d9d64991..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_arrow_patches/fancyarrow_test_image.svg +++ /dev/null @@ -1,2891 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 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/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/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 d3260ae11ee6..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_axes/axvspan_epoch.svg +++ /dev/null @@ -1,753 +0,0 @@ - - - - - - - - 2024-07-07T03:36:36.965429 - image/svg+xml - - - Matplotlib v0.1.0.dev50519+g9c53d4f.d20240707, https://matplotlib.org/ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 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 c34dddea28c9..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_rc_parameters.pdf b/lib/matplotlib/tests/baseline_images/test_axes/boxplot_rc_parameters.pdf deleted file mode 100644 index c424bc5e982f..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.svg b/lib/matplotlib/tests/baseline_images/test_axes/boxplot_rc_parameters.svg deleted file mode 100644 index c3a3cda7a9a0..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_axes/boxplot_rc_parameters.svg +++ /dev/null @@ -1,535 +0,0 @@ - - - - - - - - 2024-04-17T16:38:51.018485 - image/svg+xml - - - Matplotlib v3.9.0.dev1517+g1fa7dd164e.d20240417, https://matplotlib.org/ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 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/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 361aa8826a73..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_axes/errorbar_basic.svg +++ /dev/null @@ -1,1234 +0,0 @@ - - - - - - - - 2024-07-07T03:36:59.370109 - image/svg+xml - - - Matplotlib v0.1.0.dev50519+g9c53d4f.d20240707, https://matplotlib.org/ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 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 d7f2f4a27f92..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_axes/errorbar_limits.svg +++ /dev/null @@ -1,1702 +0,0 @@ - - - - - - - - 2024-07-07T03:37:00.187406 - image/svg+xml - - - Matplotlib v0.1.0.dev50519+g9c53d4f.d20240707, https://matplotlib.org/ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 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 c145cbbeb74d..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.svg b/lib/matplotlib/tests/baseline_images/test_axes/errorbar_mixed.svg deleted file mode 100644 index d28b10e07376..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_axes/errorbar_mixed.svg +++ /dev/null @@ -1,2391 +0,0 @@ - - - - - - - - 2024-07-17T16:09:13.200518 - image/svg+xml - - - Matplotlib v0.1.0.dev50528+ga1cfe8b, https://matplotlib.org/ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 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 index 28bde8bf76ec..605203b3c733 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/extent_units.png 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 6a624dea46c8..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.svg b/lib/matplotlib/tests/baseline_images/test_axes/fill_between_interpolate.svg deleted file mode 100644 index 2e77acfd7601..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_axes/fill_between_interpolate.svg +++ /dev/null @@ -1,1267 +0,0 @@ - - - - - - - - 2021-02-17T21:57:55.184111 - image/svg+xml - - - Matplotlib v3.3.4.post2378.dev0+g01d3149b6, https://matplotlib.org/ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 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.pdf b/lib/matplotlib/tests/baseline_images/test_axes/fill_between_interpolate_nan.pdf deleted file mode 100644 index dffa63bd7972..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/fill_between_interpolate_nan.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/fill_between_interpolate_nan.svg b/lib/matplotlib/tests/baseline_images/test_axes/fill_between_interpolate_nan.svg deleted file mode 100644 index a200927ef2fe..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_axes/fill_between_interpolate_nan.svg +++ /dev/null @@ -1,352 +0,0 @@ - - - - - - - - 2021-02-17T21:51:47.989640 - image/svg+xml - - - Matplotlib v3.3.4.post2378.dev0+g01d3149b6, https://matplotlib.org/ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 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/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 183b072fc312..f50aff2b4190 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.png b/lib/matplotlib/tests/baseline_images/test_axes/imshow.png index c19c4e069b15..d6f4bb78250e 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/imshow.png and b/lib/matplotlib/tests/baseline_images/test_axes/imshow.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/imshow.svg b/lib/matplotlib/tests/baseline_images/test_axes/imshow.svg index 3931a1fce23f..ba0ebee53d2c 100644 --- a/lib/matplotlib/tests/baseline_images/test_axes/imshow.svg +++ b/lib/matplotlib/tests/baseline_images/test_axes/imshow.svg @@ -1,12 +1,23 @@ - - + + + + + + 2026-01-30T01:51:17.751114 + image/svg+xml + + + Matplotlib v3.11.0.dev1729+g1f7cad29d, 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,40 +87,40 @@ L 0 3.5 - +" style="stroke: #000000; stroke-width: 0.8"/> - + - + - + - + - + @@ -117,28 +128,28 @@ 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"/> - - + + 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 f4bbc73544a5..f0657a43bc00 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 cde64b03c7f6..f548345db213 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 d1169e860808..bbb76c51ab5e 100644 --- a/lib/matplotlib/tests/baseline_images/test_axes/imshow_clip.svg +++ b/lib/matplotlib/tests/baseline_images/test_axes/imshow_clip.svg @@ -1,23 +1,23 @@ - + - + - 2021-03-02T20:09:49.859581 + 2026-01-30T01:51:18.376430 image/svg+xml - Matplotlib v3.3.4.post2495+g8432e3164, https://matplotlib.org/ + Matplotlib v3.11.0.dev1729+g1f7cad29d, https://matplotlib.org/ - + @@ -26,7 +26,7 @@ L 460.8 345.6 L 460.8 0 L 0 0 z -" style="fill:#ffffff;"/> +" style="fill: #ffffff"/> @@ -35,29 +35,29 @@ 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"/> - + - + - +" transform="scale(0.015625)"/> @@ -86,14 +86,14 @@ z - + - + - +" transform="scale(0.015625)"/> - + - + - + - +" transform="scale(0.015625)"/> - + - + - + - +" transform="scale(0.015625)"/> - + - + - + - +" transform="scale(0.015625)"/> - + @@ -264,17 +264,17 @@ z - +" style="stroke: #000000; stroke-width: 0.8"/> - + - + @@ -282,62 +282,62 @@ L -3.5 0 - + - + - + - + - + - + - + - + - + - + - + - + - - + +" clip-path="url(#p647eeecee3)" style="fill: none; stroke: #440154; 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_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.pdf b/lib/matplotlib/tests/baseline_images/test_axes/markevery_linear_scales_nans.pdf deleted file mode 100644 index bd4feafd6aa0..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/markevery_linear_scales_nans.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/markevery_linear_scales_nans.svg b/lib/matplotlib/tests/baseline_images/test_axes/markevery_linear_scales_nans.svg deleted file mode 100644 index c52aaf9de094..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_axes/markevery_linear_scales_nans.svg +++ /dev/null @@ -1,3581 +0,0 @@ - - - - - - - - 2022-03-28T18:57:12.789026 - image/svg+xml - - - Matplotlib v3.6.0.dev1706+g252085fd25, https://matplotlib.org/ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 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/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/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/specgram_angle_freqs.png b/lib/matplotlib/tests/baseline_images/test_axes/specgram_angle_freqs.png index eb1a29a7a83c..2c458150af68 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/specgram_angle_freqs.png and b/lib/matplotlib/tests/baseline_images/test_axes/specgram_angle_freqs.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/specgram_angle_noise.png b/lib/matplotlib/tests/baseline_images/test_axes/specgram_angle_noise.png index a9e3c4d20791..465a029c8e98 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/specgram_angle_noise.png and b/lib/matplotlib/tests/baseline_images/test_axes/specgram_angle_noise.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/specgram_freqs.png b/lib/matplotlib/tests/baseline_images/test_axes/specgram_freqs.png index 6e3a21bcace3..a766ce6f0e42 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/specgram_freqs.png and b/lib/matplotlib/tests/baseline_images/test_axes/specgram_freqs.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/specgram_freqs_linear.png b/lib/matplotlib/tests/baseline_images/test_axes/specgram_freqs_linear.png index 6e3a21bcace3..ffbaea3bfdfa 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/specgram_freqs_linear.png and b/lib/matplotlib/tests/baseline_images/test_axes/specgram_freqs_linear.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/specgram_magnitude_freqs.png b/lib/matplotlib/tests/baseline_images/test_axes/specgram_magnitude_freqs.png index ce598a5fd2ab..3406a1baf496 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/specgram_magnitude_freqs.png and b/lib/matplotlib/tests/baseline_images/test_axes/specgram_magnitude_freqs.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/specgram_magnitude_freqs_linear.png b/lib/matplotlib/tests/baseline_images/test_axes/specgram_magnitude_freqs_linear.png index ce598a5fd2ab..70e503305782 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/specgram_magnitude_freqs_linear.png and b/lib/matplotlib/tests/baseline_images/test_axes/specgram_magnitude_freqs_linear.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/specgram_magnitude_noise.png b/lib/matplotlib/tests/baseline_images/test_axes/specgram_magnitude_noise.png index 4d25d92b4010..c3b484da7a24 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/specgram_magnitude_noise.png and b/lib/matplotlib/tests/baseline_images/test_axes/specgram_magnitude_noise.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/specgram_magnitude_noise_linear.png b/lib/matplotlib/tests/baseline_images/test_axes/specgram_magnitude_noise_linear.png index 4d25d92b4010..0f54ea20b947 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/specgram_magnitude_noise_linear.png and b/lib/matplotlib/tests/baseline_images/test_axes/specgram_magnitude_noise_linear.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/specgram_noise.png b/lib/matplotlib/tests/baseline_images/test_axes/specgram_noise.png index 584cf973d51d..7c253e92f9f4 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/specgram_noise.png and b/lib/matplotlib/tests/baseline_images/test_axes/specgram_noise.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/specgram_noise_linear.png b/lib/matplotlib/tests/baseline_images/test_axes/specgram_noise_linear.png index 584cf973d51d..0d40e285c5c7 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/specgram_noise_linear.png and b/lib/matplotlib/tests/baseline_images/test_axes/specgram_noise_linear.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/specgram_phase_freqs.png b/lib/matplotlib/tests/baseline_images/test_axes/specgram_phase_freqs.png index 2deeb4e12ac4..f11ccdc3fdce 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/specgram_phase_freqs.png and b/lib/matplotlib/tests/baseline_images/test_axes/specgram_phase_freqs.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/specgram_phase_noise.png b/lib/matplotlib/tests/baseline_images/test_axes/specgram_phase_noise.png index 5980e91135f5..c7ac679392e1 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/specgram_phase_noise.png and b/lib/matplotlib/tests/baseline_images/test_axes/specgram_phase_noise.png differ 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/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 12763588c0d5..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_axes/twin_axis_locators_formatters.svg +++ /dev/null @@ -1,1290 +0,0 @@ - - - - - - - - 2024-07-07T03:36:34.083981 - image/svg+xml - - - Matplotlib v0.1.0.dev50519+g9c53d4f.d20240707, https://matplotlib.org/ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 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 index c1c8074ed80c..24fb2e78b0ce 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/use_colorizer_keyword.png and b/lib/matplotlib/tests/baseline_images/test_axes/use_colorizer_keyword.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_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/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/multi_font_type3.pdf b/lib/matplotlib/tests/baseline_images/test_backend_pdf/multi_font_type3.pdf index a148a7d571b1..a1e01accabdd 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_backend_pdf/multi_font_type3.pdf 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 index e33f8d803b12..8e6826719910 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_backend_pdf/multi_font_type42.pdf and b/lib/matplotlib/tests/baseline_images/test_backend_pdf/multi_font_type42.pdf differ 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 index efc8fc9416a7..540d1d54bd18 100644 --- 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 @@ -1,13 +1,14 @@ %!PS-Adobe-3.0 EPSF-3.0 +%%LanguageLevel: 3 %%Title: multi_font_type3.eps -%%Creator: Matplotlib v3.6.0.dev2856+g4848cedd6d.d20220807, https://matplotlib.org/ -%%CreationDate: Sun Aug 7 16:45:01 2022 +%%Creator: Matplotlib v3.10.0.dev856+g03f7095b8c, https://matplotlib.org/ +%%CreationDate: Wed Oct 16 16:10:34 2024 %%Orientation: portrait -%%BoundingBox: 18 180 594 612 -%%HiResBoundingBox: 18.000000 180.000000 594.000000 612.000000 +%%BoundingBox: 0 0 576 432 +%%HiResBoundingBox: 0.000000 0.000000 576.000000 432.000000 %%EndComments %%BeginProlog -/mpldict 12 dict def +/mpldict 10 dict def mpldict begin /_d { bind def } bind def /m { moveto } _d @@ -16,429 +17,3375 @@ mpldict begin /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 +/FontName /Cmr10 def /PaintType 0 def /FontMatrix [0.00048828125 0 0 0.00048828125 0 0] def -/FontBBox [-2090 -948 3673 2524] def +/FontBBox [-90 -512 2066 1536] def /FontType 3 def -/Encoding [/space /exclam /b /a /e /h /i /n /r /T /t /w] def -/CharStrings 13 dict dup begin +/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{651 0 0 0 0 0 sc -ce} _d -/exclam{821 0 309 0 512 1493 sc -309 254 m -512 254 l -512 0 l -309 0 l -309 254 l - -309 1493 m -512 1493 l -512 838 l -492 481 l -330 481 l -309 838 l -309 1493 l - -ce} _d -/b{1300 0 186 -29 1188 1556 sc -997 559 m -997 694 969 800 913 877 c -858 954 781 993 684 993 c -587 993 510 954 454 877 c -399 800 371 694 371 559 c -371 424 399 317 454 240 c -510 163 587 125 684 125 c -781 125 858 163 913 240 c -969 317 997 424 997 559 c - -371 950 m -410 1017 458 1066 517 1098 c -576 1131 647 1147 729 1147 c -865 1147 975 1093 1060 985 c -1145 877 1188 735 1188 559 c -1188 383 1145 241 1060 133 c -975 25 865 -29 729 -29 c -647 -29 576 -13 517 19 c -458 52 410 101 371 168 c -371 0 l -186 0 l -186 1556 l -371 1556 l -371 950 l - -ce} _d -/a{1255 0 123 -29 1069 1147 sc -702 563 m -553 563 450 546 393 512 c -336 478 307 420 307 338 c -307 273 328 221 371 182 c -414 144 473 125 547 125 c -649 125 731 161 792 233 c -854 306 885 402 885 522 c -885 563 l -702 563 l - -1069 639 m -1069 0 l -885 0 l -885 170 l -843 102 791 52 728 19 c -665 -13 589 -29 498 -29 c -383 -29 292 3 224 67 c -157 132 123 218 123 326 c -123 452 165 547 249 611 c -334 675 460 707 627 707 c -885 707 l -885 725 l -885 810 857 875 801 921 c -746 968 668 991 567 991 c -503 991 441 983 380 968 c -319 953 261 930 205 899 c -205 1069 l -272 1095 338 1114 401 1127 c -464 1140 526 1147 586 1147 c -748 1147 869 1105 949 1021 c -1029 937 1069 810 1069 639 c - -ce} _d -/e{1260 0 113 -29 1151 1147 sc -1151 606 m -1151 516 l -305 516 l -313 389 351 293 419 226 c -488 160 583 127 705 127 c -776 127 844 136 910 153 c -977 170 1043 196 1108 231 c -1108 57 l -1042 29 974 8 905 -7 c -836 -22 765 -29 694 -29 c -515 -29 374 23 269 127 c -165 231 113 372 113 549 c -113 732 162 878 261 985 c -360 1093 494 1147 662 1147 c -813 1147 932 1098 1019 1001 c -1107 904 1151 773 1151 606 c - -967 660 m -966 761 937 841 882 901 c -827 961 755 991 664 991 c -561 991 479 962 417 904 c -356 846 320 764 311 659 c -967 660 l - -ce} _d -/h{1298 0 186 0 1124 1556 sc -1124 676 m -1124 0 l -940 0 l -940 670 l -940 776 919 855 878 908 c -837 961 775 987 692 987 c -593 987 514 955 457 892 c -400 829 371 742 371 633 c -371 0 l -186 0 l -186 1556 l -371 1556 l -371 946 l -415 1013 467 1064 526 1097 c -586 1130 655 1147 733 1147 c -862 1147 959 1107 1025 1027 c -1091 948 1124 831 1124 676 c - -ce} _d -/i{569 0 193 0 377 1556 sc -193 1120 m -377 1120 l -377 0 l -193 0 l -193 1120 l - -193 1556 m -377 1556 l -377 1323 l -193 1323 l -193 1556 l - -ce} _d -/n{1298 0 186 0 1124 1147 sc -1124 676 m -1124 0 l -940 0 l -940 670 l -940 776 919 855 878 908 c -837 961 775 987 692 987 c -593 987 514 955 457 892 c -400 829 371 742 371 633 c -371 0 l -186 0 l -186 1120 l -371 1120 l -371 946 l -415 1013 467 1064 526 1097 c -586 1130 655 1147 733 1147 c -862 1147 959 1107 1025 1027 c -1091 948 1124 831 1124 676 c - -ce} _d -/r{842 0 186 0 842 1147 sc -842 948 m -821 960 799 969 774 974 c -750 980 723 983 694 983 c -590 983 510 949 454 881 c -399 814 371 717 371 590 c -371 0 l -186 0 l -186 1120 l -371 1120 l -371 946 l -410 1014 460 1064 522 1097 c -584 1130 659 1147 748 1147 c -761 1147 775 1146 790 1144 c -805 1143 822 1140 841 1137 c -842 948 l - -ce} _d -/T{1251 0 -6 0 1257 1493 sc --6 1493 m -1257 1493 l -1257 1323 l -727 1323 l +/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 -524 0 l -524 1323 l --6 1323 l --6 1493 l - -ce} _d -/t{803 0 55 0 754 1438 sc -375 1438 m -375 1120 l -754 1120 l -754 977 l -375 977 l -375 369 l -375 278 387 219 412 193 c -437 167 488 154 565 154 c -754 154 l -754 0 l -565 0 l -423 0 325 26 271 79 c -217 132 190 229 190 369 c -190 977 l -55 977 l -55 1120 l -190 1120 l -190 1438 l -375 1438 l - -ce} _d -/w{1675 0 86 0 1589 1120 sc -86 1120 m -270 1120 l -500 246 l -729 1120 l -946 1120 l -1176 246 l -1405 1120 l -1589 1120 l -1296 0 l -1079 0 l -838 918 l -596 0 l -379 0 l -86 1120 l +63 0 l ce} _d -end readonly def +/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 -/BuildGlyph { - exch begin - CharStrings exch - 2 copy known not {pop /.notdef} if - true 3 1 roll get exec - end -} _d +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 -/BuildChar { - 1 index /Encoding get exch get - 1 index /BuildGlyph get exec -} _d +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 -FontName currentdict end definefont pop -%!PS-Adobe-3.0 Resource-Font -%%Creator: Converted from TrueType to Type 3 by Matplotlib. -10 dict begin -/FontName /WenQuanYiZenHei def -/PaintType 0 def -/FontMatrix [0.0009765625 0 0 0.0009765625 0 0] def -/FontBBox [-129 -304 1076 986] def -/FontType 3 def -/Encoding [/uni51E0 /uni6C49 /uni4E2A /uni5B57] def -/CharStrings 5 dict dup begin -/.notdef 0 def -/uni51E0{1024 0 34 -126 1004 818 sc -935 257 m -947 232 970 219 1004 217 c -974 -26 l -973 -40 968 -51 960 -60 c -955 -63 950 -65 943 -65 c -729 -65 l -697 -65 670 -55 649 -34 c -628 -14 617 12 615 43 c -614 75 613 193 613 397 c -613 602 614 719 616 748 c -393 748 l -394 395 l -391 256 361 144 302 58 c -250 -21 184 -82 104 -126 c -89 -96 66 -77 34 -68 c -119 -34 188 23 240 103 c -292 184 318 281 318 395 c -317 818 l -692 818 l -692 55 l -692 42 695 30 702 20 c -709 11 719 6 730 6 c -886 6 l -898 7 905 12 908 23 c -911 42 912 62 913 81 c -935 257 l - -ce} _d -/uni6C49{1024 0 17 -119 990 820 sc -612 211 m -536 349 488 512 468 699 c -447 699 426 698 405 697 c -407 719 407 741 405 763 c -445 761 485 760 526 760 c -871 760 l -851 534 793 348 696 202 c -769 91 867 2 990 -65 c -963 -76 942 -94 928 -119 c -819 -56 727 31 653 143 c -561 27 446 -58 307 -112 c -289 -89 268 -69 243 -53 c -392 -2 515 86 612 211 c - -535 700 m -552 534 592 391 655 271 c -735 396 782 539 796 700 c -535 700 l - -151 -118 m -123 -102 88 -93 47 -92 c -76 -38 107 24 138 93 c -169 162 215 283 274 454 c -315 433 l -199 54 l -151 -118 l - -230 457 m -166 408 l -17 544 l -80 594 l -230 457 l - -248 626 m -202 677 152 724 97 768 c -157 820 l -214 773 268 723 317 670 c -248 626 l - -ce} _d -/uni4E2A{1024 0 14 -123 980 833 sc -547 -123 m -520 -120 492 -120 464 -123 c -467 -72 468 -21 468 30 c -468 362 l -468 413 467 465 464 516 c -492 513 520 513 547 516 c -545 465 544 413 544 362 c -544 30 l -544 -21 545 -72 547 -123 c - -980 427 m -955 410 939 387 931 358 c -846 384 767 429 695 494 c -624 559 563 631 514 711 c -383 520 236 378 71 285 c -59 314 40 337 14 354 c -113 405 204 471 285 550 c -367 630 433 724 484 833 c -499 822 515 813 531 805 c -537 808 l -542 800 l -549 796 557 792 564 789 c -555 775 l -614 672 682 590 759 531 c -824 484 898 450 980 427 c - -ce} _d -/uni5B57{1024 0 32 -132 982 872 sc -982 285 m -980 264 980 243 982 222 c -943 224 904 225 865 225 c -555 225 l -555 -29 l -555 -54 545 -76 525 -95 c -496 -120 444 -132 368 -131 c -376 -98 368 -68 344 -43 c -366 -46 392 -47 422 -47 c -452 -48 470 -46 475 -42 c -480 -38 483 -27 483 -9 c -483 225 l -148 225 l -109 225 71 224 32 222 c -34 243 34 264 32 285 c -71 283 109 282 148 282 c -483 282 l -483 355 l -648 506 l -317 506 l -278 506 239 505 200 503 c -203 524 203 545 200 566 c -239 564 278 563 317 563 c -761 563 l -769 498 l -748 493 730 483 714 469 c -555 323 l -555 282 l -865 282 l -904 282 943 283 982 285 c - -131 562 m -59 562 l -59 753 l -468 752 l -390 807 l -435 872 l -542 798 l -510 752 l -925 752 l -925 562 l -852 562 l -852 695 l -131 695 l -131 562 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 @@ -457,64 +3404,2309 @@ end readonly def } _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 -18 180 translate -576 432 0 0 clipbox +0 0 translate +0 0 576 432 rectclip gsave 0 0 m 576 0 l 576 432 l 0 432 l cl -1.000 setgray +1 setgray fill grestore -0.000 setgray -/DejaVuSans 27.000 selectfont +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 -86.4 205.2 translate +118.953 75.8109 translate 0 rotate -0.000000 0 m /T glyphshow -16.492676 0 m /h glyphshow -33.604980 0 m /e glyphshow -50.216309 0 m /r glyphshow -61.316895 0 m /e glyphshow -77.928223 0 m /space glyphshow -86.510742 0 m /a glyphshow -103.056152 0 m /r glyphshow -114.156738 0 m /e glyphshow -130.768066 0 m /space glyphshow +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 -/WenQuanYiZenHei 27.000 selectfont +/DejaVuSans 16.000 selectfont gsave -86.4 205.2 translate +213.938 56.1484 translate 0 rotate -139.350586 0 m /uni51E0 glyphshow -166.350586 0 m /uni4E2A glyphshow -193.350586 0 m /uni6C49 glyphshow -220.350586 0 m /uni5B57 glyphshow +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 -/DejaVuSans 27.000 selectfont +/Cmr10 16.000 selectfont gsave -86.4 205.2 translate +248.008 38.9422 translate 0 rotate -247.350586 0 m /space glyphshow -255.933105 0 m /i glyphshow -263.434570 0 m /n glyphshow -280.546875 0 m /space glyphshow -289.129395 0 m /b glyphshow -306.268066 0 m /e glyphshow -322.879395 0 m /t glyphshow -333.465820 0 m /w glyphshow -355.548340 0 m /e glyphshow -372.159668 0 m /e glyphshow -388.770996 0 m /n glyphshow -405.883301 0 m /exclam glyphshow +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 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 index 472824330da4..e448344deeb9 100644 --- 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 @@ -1,13 +1,14 @@ %!PS-Adobe-3.0 EPSF-3.0 +%%LanguageLevel: 3 %%Title: multi_font_type42.eps -%%Creator: Matplotlib v3.6.0.dev2856+g4848cedd6d.d20220807, https://matplotlib.org/ -%%CreationDate: Sun Aug 7 16:45:01 2022 +%%Creator: Matplotlib v3.10.0.dev856+g03f7095b8c, https://matplotlib.org/ +%%CreationDate: Wed Oct 16 16:10:36 2024 %%Orientation: portrait -%%BoundingBox: 18 180 594 612 -%%HiResBoundingBox: 18.000000 180.000000 594.000000 612.000000 +%%BoundingBox: 0 0 576 432 +%%HiResBoundingBox: 0.000000 0.000000 576.000000 432.000000 %%EndComments %%BeginProlog -/mpldict 12 dict def +/mpldict 10 dict def mpldict begin /_d { bind def } bind def /m { moveto } _d @@ -16,345 +17,2813 @@ mpldict begin /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: unknown -%%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 (unknown) def -/FullName (unknown) def -/Weight (unknown) def -/Version (unknown) def -/ItalicAngle 0.0 def -/isFixedPitch false def -/UnderlinePosition -130 def -/UnderlineThickness 90 def -end readonly def -/sfnts[<0001000000090080000300106376742000691D390000009C000001FE6670676D -7134766A0000029C000000AB676C7966118399D500000348000007B668656164085DC286 -00000B0000000036686865610D9F077C00000B3800000024686D74783A5706B700000B5C -0000003C6C6F636108C30B6500000B98000000206D617870047C067100000BB800000020 -707265703B07F10000000BD800000568013500B800CB00CB00C100AA009C01A600B80066 -0000007100CB00A002B20085007500B800C301CB0189022D00CB00A600F000D300AA0087 -00CB03AA0400014A003300CB000000D9050200F4015400B4009C01390114013907060400 -044E04B4045204B804E704CD0037047304CD04600473013303A2055605A60556053903C5 -021200C9001F00B801DF007300BA03E9033303BC0444040E00DF03CD03AA00E503AA0404 -000000CB008F00A4007B00B80014016F007F027B0252008F00C705CD009A009A006F00CB -00CD019E01D300F000BA018300D5009803040248009E01D500C100CB00F600830354027F -00000333026600D300C700A400CD008F009A0073040005D5010A00FE022B00A400B4009C -00000062009C0000001D032D05D505D505D505F0007F007B005400A406B80614072301D3 -00B800CB00A601C301EC069300A000D3035C037103DB0185042304A80448008F01390114 -01390360008F05D5019A0614072306660179046004600460047B009C00000277046001AA -00E904600762007B00C5007F027B000000B4025205CD006600BC00660077061000CD013B -01850389008F007B0000001D00CD074A042F009C009C0000077D006F0000006F0335006A -006F007B00AE00B2002D0396008F027B00F600830354063705F6008F009C04E10266008F -018D02F600CD03440029006604EE00730000140000960000B707060504030201002C2010 -B002254964B040515820C859212D2CB002254964B040515820C859212D2C20100720B000 -50B00D7920B8FFFF5058041B0559B0051CB0032508B0042523E120B00050B00D7920B8FF -FF5058041B0559B0051CB0032508E12D2C4B505820B0FD454459212D2CB002254560442D -2C4B5358B00225B0022545445921212D2C45442D2CB00225B0022549B00525B005254960 -B0206368208A108A233A8A10653A2D00000201350000020005D5000300090035400F0700 -8304810208070501030400000A10FC4BB00B5458B90000FFC038593CEC32393931002FE4 -FCCC3001B6000B200B500B035D253315231133110323030135CBCBCB14A215FEFE05D5FD -71FE9B0165000001FFFA000004E905D50007004A400E0602950081040140031C00400508 -10D4E4FCE431002FF4EC3230014BB00A5458BD00080040000100080008FFC03811373859 -401300091F00100110021F071009400970099F09095D03211521112311210604EFFDEECB -FDEE05D5AAFAD5052B000002007BFFE3042D047B000A002500BC4027191F0B17090E00A9 -1706B90E1120861FBA1CB923B8118C170C001703180D09080B1F030814452610FCECCCD4 -EC323211393931002FC4E4F4FCF4EC10C6EE10EE11391139123930406E301D301E301F30 -20302130223F27401D401E401F402040214022501D501E501F5020502150225027702785 -1D871E871F8720872185229027A027F0271E301E301F30203021401E401F40204021501E -501F50205021601E601F60206021701E701F70207021801E801F80208021185D015D0122 -061514163332363D01371123350E01232226353436332135342623220607353E01333216 -02BEDFAC816F99B9B8B83FBC88ACCBFDFB0102A79760B65465BE5AF3F00233667B6273D9 -B4294CFD81AA6661C1A2BDC0127F8B2E2EAA2727FC00000200BAFFE304A40614000B001C -0038401903B90C0F09B918158C0FB81B971900121247180C06081A461D10FCEC3232F4EC -31002FECE4F4C4EC10C6EE30B6601E801EA01E03015D013426232206151416333236013E -01333200111002232226271523113303E5A79292A7A79292A7FD8E3AB17BCC00FFFFCC7B -B13AB9B9022FCBE7E7CBCBE7E702526461FEBCFEF8FEF8FEBC6164A8061400020071FFE3 -047F047B0014001B00704024001501098608880515A90105B90C01BB18B912B80C8C1C1B -1502081508004B02120F451C10FCECF4ECC4111239310010E4F4ECE410EE10EE10F4EE11 -12393040293F1D701DA01DD01DF01D053F003F013F023F153F1B052C072F082F092C0A6F -006F016F026F156F1B095D71015D0115211E0133323637150E0123200011100033320007 -2E0123220607047FFCB20CCDB76AC76263D06BFEF4FEC70129FCE20107B802A5889AB90E -025E5ABEC73434AE2A2C0138010A01130143FEDDC497B4AE9E00000100BA000004640614 -001300344019030900030E0106870E11B80C970A010208004E0D09080B461410FCEC32F4 -EC31002F3CECF4C4EC1112173930B2601501015D0111231134262322061511231133113E -013332160464B87C7C95ACB9B942B375C1C602A4FD5C029E9F9EBEA4FD870614FD9E6564 -EF00000200C100000179061400030007002B400E06BE04B100BC020501080400460810FC -3CEC3231002FE4FCEC30400B1009400950096009700905015D1333112311331523C1B8B8 -B8B80460FBA00614E900000100BA00000464047B001300364019030900030E0106870E11 -B80CBC0A010208004E0D09080B461410FCEC32F4EC31002F3CE4F4C4EC1112173930B460 -15CF1502015D0111231134262322061511231133153E013332160464B87C7C95ACB9B942 -B375C1C602A4FD5C029E9F9EBEA4FD870460AE6564EF000100BA0000034A047B00110030 -4014060B0700110B03870EB809BC070A06080008461210FCC4EC3231002FE4F4ECC4D4CC -11123930B450139F1302015D012E012322061511231133153E0133321617034A1F492C9C -A7B9B93ABA85132E1C03B41211CBBEFDB20460AE6663050500010037000002F2059E0013 -003840190E05080F03A9001101BC08870A0B08090204000810120E461410FC3CC4FC3CC4 -32393931002FECF43CC4EC3211393930B2AF1501015D01112115211114163B0115232226 -3511233533110177017BFE854B73BDBDD5A28787059EFEC28FFDA0894E9A9FD202608F01 -3E0000010056000006350460000C01EB404905550605090A0904550A0903550A0B0A0255 -01020B0B0A061107080705110405080807021103020C000C011100000C420A0502030603 -00BF0B080C0B0A09080605040302010B07000D10D44BB00A544BB011545B4BB012545B4B -B013545B4BB00B545B58B9000000403859014BB00C544BB00D545B4BB010545B58B90000 -FFC03859CC173931002F3CEC32321739304B5358071005ED071008ED071008ED071005ED -071008ED071005ED0705ED071008ED59220140FF050216021605220A350A49024905460A -400A5B025B05550A500A6E026E05660A79027F0279057F05870299029805940ABC02BC05 -CE02C703CF051D0502090306040B050A080B09040B050C1502190316041A051B081B0914 -0B150C2500250123022703210425052206220725082709240A210B230C39033604360839 -0C300E460248034604400442054006400740084409440A440B400E400E56005601560250 -0451055206520750085309540A550B6300640165026A0365046A056A066A076E09610B67 -0C6F0E7500750179027D0378047D057A067F067A077F07780879097F097B0A760B7D0C87 -0288058F0E97009701940293039C049B05980698079908402F960C9F0EA600A601A402A4 -03AB04AB05A906A907AB08A40CAF0EB502B103BD04BB05B809BF0EC402C303CC04CA0579 -5D005D13331B01331B013301230B012356B8E6E5D9E6E5B8FEDBD9F1F2D90460FC96036A -FC96036AFBA00396FC6A00000001000000025999D203C60C5F0F3CF5001F080000000000 -D17E0EE400000000D17E0EE4F7D6FC4C0E5909DC00000008000000000000000000010000 -076DFE1D00000EFEF7D6FA510E5900010000000000000000000000000000000F04CD0066 -0000000002AA0000028B00000335013504E3FFFA04E7007B051400BA04EC0071051200BA -023900C1051200BA034A00BA03230037068B0056000000000000000000000031006900FF -014B01B501F102190255028C02C903DB00010000000F0354002B0068000C000200100099 -000800000415021600080004B8028040FFFBFE03FA1403F92503F83203F79603F60E03F5 -FE03F4FE03F32503F20E03F19603F02503EF8A4105EFFE03EE9603ED9603ECFA03EBFA03 -EAFE03E93A03E84203E7FE03E63203E5E45305E59603E48A4105E45303E3E22F05E3FA03 -E22F03E1FE03E0FE03DF3203DE1403DD9603DCFE03DB1203DA7D03D9BB03D8FE03D68A41 -05D67D03D5D44705D57D03D44703D3D21B05D3FE03D21B03D1FE03D0FE03CFFE03CEFE03 -CD9603CCCB1E05CCFE03CB1E03CA3203C9FE03C6851105C61C03C51603C4FE03C3FE03C2 -FE03C1FE03C0FE03BFFE03BEFE03BDFE03BCFE03BBFE03BA1103B9862505B9FE03B8B7BB -05B8FE03B7B65D05B7BB03B78004B6B52505B65D40FF03B64004B52503B4FE03B39603B2 -FE03B1FE03B0FE03AFFE03AE6403AD0E03ACAB2505AC6403ABAA1205AB2503AA1203A98A -4105A9FA03A8FE03A7FE03A6FE03A51203A4FE03A3A20E05A33203A20E03A16403A08A41 -05A096039FFE039E9D0C059EFE039D0C039C9B19059C64039B9A10059B19039A1003990A -0398FE0397960D0597FE03960D03958A410595960394930E05942803930E0392FA039190 -BB0591FE03908F5D0590BB039080048F8E25058F5D038F40048E25038DFE038C8B2E058C -FE038B2E038A8625058A410389880B05891403880B038786250587640386851105862503 -85110384FE038382110583FE0382110381FE0380FE037FFE0340FF7E7D7D057EFE037D7D -037C64037B5415057B25037AFE0379FE03780E03770C03760A0375FE0374FA0373FA0372 -FA0371FA0370FE036FFE036EFE036C21036BFE036A1142056A530369FE03687D03671142 -0566FE0365FE0364FE0363FE0362FE03613A0360FA035E0C035DFE035BFE035AFE035958 -0A0559FA03580A035716190557320356FE03555415055542035415035301100553180352 -1403514A130551FE03500B034FFE034E4D10054EFE034D10034CFE034B4A13054BFE034A -4910054A1303491D0D05491003480D0347FE0346960345960344FE0343022D0543FA0342 -BB03414B0340FE033FFE033E3D12053E14033D3C0F053D12033C3B0D053C40FF0F033B0D -033AFE0339FE033837140538FA033736100537140336350B05361003350B03341E03330D -0332310B0532FE03310B03302F0B05300D032F0B032E2D09052E10032D09032C32032B2A -25052B64032A2912052A25032912032827250528410327250326250B05260F03250B0324 -FE0323FE03220F03210110052112032064031FFA031E1D0D051E64031D0D031C1142051C -FE031BFA031A42031911420519FE031864031716190517FE031601100516190315FE0314 -FE0313FE031211420512FE0311022D05114203107D030F64030EFE030D0C16050DFE030C -0110050C16030BFE030A100309FE0308022D0508FE030714030664030401100504FE0340 -1503022D0503FE0302011005022D0301100300FE0301B80164858D012B2B2B2B2B2B2B2B -2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B -2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B -2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B -2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B -2B2B2B2B2B2B2B2B2B2B2B2B2B002B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B -2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B -2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B -2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B -2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B2B1D00>]def -/CharStrings 13 dict dup begin + + %%!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 -/space 3 def -/exclam 4 def -/b 7 def -/a 6 def -/e 8 def -/h 9 def -/i 10 def -/n 11 def -/r 12 def -/T 5 def -/t 13 def -/w 14 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 -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-0.58982 -%%Title: unknown -%%Creator: Converted from TrueType to type 42 by PPR -15 dict begin -/FontName /WenQuanYiZenHei def -/PaintType 0 def -/FontMatrix[1 0 0 1 0 0]def -/FontBBox[-126 -297 1051 963]def -/FontType 42 def -/Encoding StandardEncoding def -/FontInfo 10 dict dup begin -/FamilyName (unknown) def -/FullName (unknown) def -/Weight (unknown) def -/Version (unknown) def -/ItalicAngle 0.0 def -/isFixedPitch false def -/UnderlinePosition -230 def -/UnderlineThickness 51 def -end readonly def -/sfnts[<00010000000700400002003063767420002202880000007C00000004676C7966 -AC20EA39000000800000024A68656164F2831BDF000002CC000000366868656107EC01A3 -0000030400000024686D747805A7004E000003280000001A6C6F636101A0011100000344 -000000126D617870008A02690000035800000020002202880002000DFF8503D50341000D -0024000005260736351134271637061511140106072E0127020726273E01371617371716 -17071617160223292A04042A290301B4250C80D74AC4F7122795F54C171806050B0B0958 -74627B04044D4C014C4D4D04044D4DFEB44C01D91A2B27C278FEE18B2C194DEFA3100C03 -0806050E9B59460000010021FF8203EC033300230000011617070607062B01222E011037 -23130607060726273E013503211114163B013637363703A712331E020C070AD6303F0503 -DF0104584E781630809C01017715119C1204040101012503F3150D053D5F02652CFE9FD0 -8176422D0D33F1AB01A7FD05141D01101D1D0002001FFF7C03D60369002A003700000106 -17262321151407062736271E01363D012122073627163321353721220736271633211706 -0F011521320123350527371707211523352103D603033A3BFECA1E2B720C24215A10FEB1 -3A3A03033A3A014FA5FEB53A3B04043B3A01BC081F189F01363BFCE74801994E2D6B2001 -9F49FD2F011D1F2003FE261C2501322604010C1BEA03201F03499703201F034108159229 -0118BF0137414A2EBE8500050010FF8803DF03350016001B00230027002D000025260322 -0736271633210207161706072627060726273613161736370126273E011317031307273F -0126273716170264721E201F03033C3D01591E916EB82915A46F8AD01B25E0441A5E7815 -FD7B2A3E2C5E5929741F40953FA845523C564AD3CF011902212103FEADDBA66510265EA8 -AE5123184D02A4F9B4BBF2FCCE180251D0010115FE850193318832204C42344650000000 -000100000000E666EDAC36235F0F3CF5003F040000000000C7BE78E900000000C7BE78E9 -FF7FFED0043403DA0000000800020000000000000001000003DAFED0005C0455FF7FFE78 -043400010000000000000000000000000000000501760022000000000000000000000000 -0400000D0021001F00100000000000000000000000000040007B00D10125000000010000 -00080165002800D10012000200000001000100000040002E0006000200>]def -/CharStrings 5 dict dup begin + %%!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 -/uni51E0 5 def -/uni6C49 7 def -/uni4E2A 4 def -/uni5B57 6 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 - -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 + /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 -18 180 translate -576 432 0 0 clipbox +0 0 translate +0 0 576 432 rectclip gsave 0 0 m 576 0 l 576 432 l 0 432 l cl -1.000 setgray +1 setgray fill grestore -0.000 setgray -/DejaVuSans 27.000 selectfont +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 -86.4 205.2 translate +118.953 75.8109 translate 0 rotate -0.000000 0 m /T glyphshow -16.492676 0 m /h glyphshow -33.604980 0 m /e glyphshow -50.216309 0 m /r glyphshow -61.316895 0 m /e glyphshow -77.928223 0 m /space glyphshow -86.510742 0 m /a glyphshow -103.056152 0 m /r glyphshow -114.156738 0 m /e glyphshow -130.768066 0 m /space glyphshow +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 -/WenQuanYiZenHei 27.000 selectfont +/DejaVuSans 16.000 selectfont gsave -86.4 205.2 translate +213.938 56.1484 translate 0 rotate -139.350586 0 m /uni51E0 glyphshow -166.350586 0 m /uni4E2A glyphshow -193.350586 0 m /uni6C49 glyphshow -220.350586 0 m /uni5B57 glyphshow +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 -/DejaVuSans 27.000 selectfont +/Cmr10 16.000 selectfont gsave -86.4 205.2 translate +248.008 38.9422 translate 0 rotate -247.350586 0 m /space glyphshow -255.933105 0 m /i glyphshow -263.434570 0 m /n glyphshow -280.546875 0 m /space glyphshow -289.129395 0 m /b glyphshow -306.268066 0 m /e glyphshow -322.879395 0 m /t glyphshow -333.465820 0 m /w glyphshow -355.548340 0 m /e glyphshow -372.159668 0 m /e glyphshow -388.770996 0 m /n glyphshow -405.883301 0 m /exclam glyphshow +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 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 index 7184700caf17..b1e4fecfd2f4 100644 --- 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 @@ -6,11 +6,11 @@ - 2022-08-09T18:12:28.920123 + 2024-09-05T21:08:26.637648 image/svg+xml - Matplotlib v3.6.0.dev2839+gb0bf8fb1de.d20220809, https://matplotlib.org/ + Matplotlib v3.10.0.dev632+g9c5136f7df.d20240906, https://matplotlib.org/ @@ -29,23 +29,14658 @@ z " style="fill: #ffffff"/> - - + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + - - - - - - - - - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 index 373103f61b9f..c9902ca1b806 100644 --- 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 @@ -6,11 +6,11 @@ - 2022-08-09T18:42:37.025191 + 2024-09-05T21:08:27.107851 image/svg+xml - Matplotlib v3.6.0.dev2840+g372782e258.d20220809, https://matplotlib.org/ + Matplotlib v3.10.0.dev632+g9c5136f7df.d20240906, https://matplotlib.org/ @@ -29,7 +29,24 @@ z " style="fill: #ffffff"/> - There are 几个汉字 in between! + 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_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..46facee23546 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_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 9ceeb930cef2..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_collections/EventCollection_plot__append_positions.svg +++ /dev/null @@ -1,859 +0,0 @@ - - - - - - - - 2024-07-07T03:41:26.264474 - image/svg+xml - - - Matplotlib v0.1.0.dev50519+g9c53d4f.d20240707, https://matplotlib.org/ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 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 aac64d958b31..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_collections/EventCollection_plot__default.svg +++ /dev/null @@ -1,731 +0,0 @@ - - - - - - - - 2024-07-07T03:41:25.382570 - image/svg+xml - - - Matplotlib v0.1.0.dev50519+g9c53d4f.d20240707, https://matplotlib.org/ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 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 c4b5c08c50c0..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_collections/EventCollection_plot__extend_positions.svg +++ /dev/null @@ -1,844 +0,0 @@ - - - - - - - - 2024-07-07T03:41:26.434024 - image/svg+xml - - - Matplotlib v0.1.0.dev50519+g9c53d4f.d20240707, https://matplotlib.org/ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 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 29a9ad0368b4..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_collections/EventCollection_plot__set_color.svg +++ /dev/null @@ -1,686 +0,0 @@ - - - - - - - - 2024-07-07T03:41:27.893155 - image/svg+xml - - - Matplotlib v0.1.0.dev50519+g9c53d4f.d20240707, https://matplotlib.org/ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 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 90b5fab01765..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_collections/EventCollection_plot__set_linelength.svg +++ /dev/null @@ -1,799 +0,0 @@ - - - - - - - - 2024-07-07T03:41:27.095376 - image/svg+xml - - - Matplotlib v0.1.0.dev50519+g9c53d4f.d20240707, https://matplotlib.org/ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 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 b8dbf48bce65..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_collections/EventCollection_plot__set_lineoffset.svg +++ /dev/null @@ -1,758 +0,0 @@ - - - - - - - - 2024-07-07T03:41:27.255825 - image/svg+xml - - - Matplotlib v0.1.0.dev50519+g9c53d4f.d20240707, https://matplotlib.org/ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 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 d9b33747f360..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_collections/EventCollection_plot__set_linewidth.svg +++ /dev/null @@ -1,734 +0,0 @@ - - - - - - - - 2024-07-07T03:41:27.736237 - image/svg+xml - - - Matplotlib v0.1.0.dev50519+g9c53d4f.d20240707, https://matplotlib.org/ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 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 e7ba87cd63c7..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_collections/EventCollection_plot__set_positions.svg +++ /dev/null @@ -1,803 +0,0 @@ - - - - - - - - 2024-07-07T03:41:25.870012 - image/svg+xml - - - Matplotlib v0.1.0.dev50519+g9c53d4f.d20240707, https://matplotlib.org/ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 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 a467edef0196..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_collections/EventCollection_plot__switch_orientation.svg +++ /dev/null @@ -1,761 +0,0 @@ - - - - - - - - 2024-07-07T03:41:26.604187 - image/svg+xml - - - Matplotlib v0.1.0.dev50519+g9c53d4f.d20240707, https://matplotlib.org/ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 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 0f7bde1e09d8..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_collections/EventCollection_plot__switch_orientation__2x.svg +++ /dev/null @@ -1,781 +0,0 @@ - - - - - - - - 2024-07-07T03:41:26.771522 - image/svg+xml - - - Matplotlib v0.1.0.dev50519+g9c53d4f.d20240707, https://matplotlib.org/ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 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 index 68cbf3d341f3..411a2511dc54 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_colorbar/colorbar_extend_alpha.png 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_keeping_xlabel.png b/lib/matplotlib/tests/baseline_images/test_colorbar/colorbar_keeping_xlabel.png index 410b9f5b0878..e3f82d44d3b4 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_colorbar/colorbar_keeping_xlabel.png and b/lib/matplotlib/tests/baseline_images/test_colorbar/colorbar_keeping_xlabel.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_colors/light_source_shading_topo.png b/lib/matplotlib/tests/baseline_images/test_colors/light_source_shading_topo.png index 222ebca02d82..b014a8f1eaa5 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_colors/light_source_shading_topo.png and b/lib/matplotlib/tests/baseline_images/test_colors/light_source_shading_topo.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_colors/test_norm_abc.png b/lib/matplotlib/tests/baseline_images/test_colors/test_norm_abc.png new file mode 100644 index 000000000000..c2169461d075 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_colors/test_norm_abc.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 f337d370dc33..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 534903300f7a..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 4233f58a8ce4..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 cfe9dca14c88..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 ae6420dd04e9..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 ef6d9e417f91..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 89e71b765154..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 a239947ca46c..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 2ac44b8a18ac..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_colorbars_no_overlapH.png b/lib/matplotlib/tests/baseline_images/test_constrainedlayout/test_colorbars_no_overlapH.png index d91bcdf22b7a..189474e52df0 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..231615afb00e 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_constrainedlayout/test_compressed_suptitle_colorbar.png b/lib/matplotlib/tests/baseline_images/test_constrainedlayout/test_compressed_suptitle_colorbar.png new file mode 100644 index 000000000000..df4ecaf939c7 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_constrainedlayout/test_compressed_suptitle_colorbar.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_constrainedlayout/test_compressed_supylabel_colorbar.png b/lib/matplotlib/tests/baseline_images/test_constrainedlayout/test_compressed_supylabel_colorbar.png new file mode 100644 index 000000000000..cd6e38e62f00 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_constrainedlayout/test_compressed_supylabel_colorbar.png differ 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_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_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/affine_fill_to_edges.png b/lib/matplotlib/tests/baseline_images/test_image/affine_fill_to_edges.png new file mode 100644 index 000000000000..88858eda5d94 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_image/affine_fill_to_edges.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_image/alignment_half_display_pixels.png b/lib/matplotlib/tests/baseline_images/test_image/alignment_half_display_pixels.png new file mode 100644 index 000000000000..e822ced620f2 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_image/alignment_half_display_pixels.png differ 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 e0baa115a6b3..4fa9dc748b02 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/bbox_image_inverted.png b/lib/matplotlib/tests/baseline_images/test_image/bbox_image_inverted.png index c593b2163997..0d2b7b459ecb 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_image/bbox_image_inverted.png and b/lib/matplotlib/tests/baseline_images/test_image/bbox_image_inverted.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_image/bbox_image_inverted.svg b/lib/matplotlib/tests/baseline_images/test_image/bbox_image_inverted.svg index 406a278f2f3f..268c9d14f605 100644 --- a/lib/matplotlib/tests/baseline_images/test_image/bbox_image_inverted.svg +++ b/lib/matplotlib/tests/baseline_images/test_image/bbox_image_inverted.svg @@ -1,12 +1,23 @@ - - + + + + + + 2026-01-30T01:57:12.969851 + image/svg+xml + + + Matplotlib v3.11.0.dev1729+g1f7cad29d, 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,57 +35,57 @@ 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"/> - + - + - + - + - + - + @@ -83,47 +94,47 @@ L 0 3.5 - +" style="stroke: #000000; stroke-width: 0.8"/> - + - + - + - + - + - + @@ -131,22 +142,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"/> diff --git a/lib/matplotlib/tests/baseline_images/test_image/downsampling.png b/lib/matplotlib/tests/baseline_images/test_image/downsampling.png index 4e68e52d787b..ecd912cb5bed 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_image/downsampling.png 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 index eb8b3ce13013..26d3fae92a6a 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_image/downsampling_speckle.png and b/lib/matplotlib/tests/baseline_images/test_image/downsampling_speckle.png 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 f5923c481fa7..f4d80f2a5ec4 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.png b/lib/matplotlib/tests/baseline_images/test_image/image_alpha.png index 880cc891887b..301a55c03ad5 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_image/image_alpha.png and b/lib/matplotlib/tests/baseline_images/test_image/image_alpha.png 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 dd6884710e69..47b489e391bb 100644 --- a/lib/matplotlib/tests/baseline_images/test_image/image_alpha.svg +++ b/lib/matplotlib/tests/baseline_images/test_image/image_alpha.svg @@ -6,11 +6,11 @@ - 2023-04-16T19:11:24.492650 + 2026-01-30T01:57:05.781655 image/svg+xml - Matplotlib v3.8.0.dev855+gc9636b5044.d20230417, https://matplotlib.org/ + Matplotlib v3.11.0.dev1729+g1f7cad29d, https://matplotlib.org/ @@ -37,7 +37,7 @@ L 72 150.352941 z " style="fill: #ffffff"/> - + @@ -239,7 +239,7 @@ L 229.552941 150.352941 z " style="fill: #ffffff"/> - + @@ -421,9 +421,9 @@ L 387.105882 150.352941 z " style="fill: #ffffff"/> - + +iVBORw0KGgoAAAANSUhEUgAAALYAAAC2CAYAAAB08HcEAAAC8klEQVR4nO3cMYqeBRSG0ftLCsHYOmBptiAIgqgIip2pBoug01gJdraDYJfUWo4MCFaDnSBYzAKyBMHOdCIopIh8KbIGufBwzgbe5uGW9zTfHpez4PjrtDE7549+WNmdmbmYq5Xdj0/vrezOzPxxfLey+9LKKvzPhE2SsEkSNknCJknYJAmbJGGTJGyShE2SsEkSNknCJknYJAmbJGGTJGyShE2SsEkSNknCJknYJAmbJGGTJGyShE2SsEkSNknCJuk08+/Kt9V/nr66MTt37/63sjsz89OznQ+zT44vVnZnZq7nwcqui02SsEkSNknCJknYJAmbJGGTJGyShE2SsEkSNknCJknYJAmbJGGTJGyShE2SsEkSNknCJknYJAmbJGGTJGyShE2SsEkSNknCJknYJAmbpDvHV6+sDB+vr8zO5dIr35mZ8093dh/MmzvDM/P4y3dWdl1skoRNkrBJEjZJwiZJ2CQJmyRhkyRskoRNkrBJEjZJwiZJ2CQJmyRhkyRskoRNkrBJEjZJwiZJ2CQJmyRhkyRskoRNkrBJEjZJwibp9Mvx7uXG8Mun243ZeX9+Xdl94Wxp92Zpd+Z465uVXRebJGGTJGyShE2SsEkSNknCJknYJAmbJGGTJGyShE2SsEkSNknCJknYJAmbJGGTJGyShE2SsEkSNknCJknYJAmbJGGTJGyShE2SsEk6fXj8vPJt9fr0ycbs/Liy+sLD4/eV3T8v7q3szsx8f/XZyq6LTZKwSRI2ScImSdgkCZskYZMkbJKETZKwSRI2ScImSdgkCZskYZMkbJKETZKwSRI2ScImSdgkCZskYZMkbJKETZKwSRI2ScImSdgkCZukO5/P1crw2c3K7Hx0/42d4Zl5bb5e2X376reV3ZmZ278/WNl1sUkSNknCJknYJAmbJGGTJGyShE2SsEkSNknCJknYJAmbJGGTJGyShE2SsEkSNknCJknYJAmbJGGTJGyShE2SsEkSNknCJknYJAmbpOcW2THVhlsXJwAAAABJRU5ErkJggg==" id="image2b7b2b7d47" transform="scale(1 -1) translate(0 -131.04)" x="387.36" y="-150.48" width="131.04" height="131.04"/> + - + - + diff --git a/lib/matplotlib/tests/baseline_images/test_image/image_bounds_handling.png b/lib/matplotlib/tests/baseline_images/test_image/image_bounds_handling.png new file mode 100644 index 000000000000..3a817f331c42 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_image/image_bounds_handling.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_image/image_clip.svg b/lib/matplotlib/tests/baseline_images/test_image/image_clip.svg index ac14e6b273f0..4d3d6a60a703 100644 --- a/lib/matplotlib/tests/baseline_images/test_image/image_clip.svg +++ b/lib/matplotlib/tests/baseline_images/test_image/image_clip.svg @@ -1,12 +1,23 @@ - - + + + + + + 2026-01-30T01:57:08.680939 + image/svg+xml + + + Matplotlib v3.11.0.dev1729+g1f7cad29d, 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,272 +35,277 @@ 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"/> - + - - + + - + - + - + - - +" transform="scale(0.015625)"/> + - - - - + + + + - + - - + + - - +" transform="scale(0.015625)"/> + - - - - + + + + - + - + - - - + + + - + - + - - - + + + - + - + - - - + + + - + - - + + - - +" transform="scale(0.015625)"/> + - - - + + + - + - - + + - - +" transform="scale(0.015625)"/> + - - - + + + - + - + - - - + + + - + - + - - - + + + @@ -298,151 +314,151 @@ z - +" style="stroke: #000000; stroke-width: 0.8"/> - + - + - - - - + + + + - + - + - - - - + + + + - + - + - - - + + + - + - + - - - + + + - + - + - - - + + + - + - + - - - + + + - + - + - - - + + + - + - + - - - + + + - + - + - - - + + + @@ -450,27 +466,27 @@ 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"/> - + - - + + + + + + 2026-01-30T01:57:09.227057 + image/svg+xml + + + Matplotlib v3.11.0.dev1729+g1f7cad29d, 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,236 +35,240 @@ 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"/> - + - - + + - - - +" transform="scale(0.015625)"/> + + - + - - + + - - - +" transform="scale(0.015625)"/> + + - + - - + + - - - +" transform="scale(0.015625)"/> + + - + - - + + - - - +" transform="scale(0.015625)"/> + + - + - - + + - - - +" transform="scale(0.015625)"/> + + - + - - + + - - - +" transform="scale(0.015625)"/> + + @@ -262,83 +277,83 @@ z - +" style="stroke: #000000; stroke-width: 0.8"/> - + - - + + - + - - + + - + - - + + - + - - + + - + - - + + - + - - + + @@ -346,28 +361,28 @@ 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"/> - - + + diff --git a/lib/matplotlib/tests/baseline_images/test_image/image_composite_alpha.pdf b/lib/matplotlib/tests/baseline_images/test_image/image_composite_alpha.pdf index e7d205bfa8e0..b8cd42f9dde4 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_image/image_composite_alpha.pdf and b/lib/matplotlib/tests/baseline_images/test_image/image_composite_alpha.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_image/image_composite_alpha.png b/lib/matplotlib/tests/baseline_images/test_image/image_composite_alpha.png index c9e9f343c5db..6c772a880861 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_image/image_composite_alpha.png and b/lib/matplotlib/tests/baseline_images/test_image/image_composite_alpha.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_image/image_composite_alpha.svg b/lib/matplotlib/tests/baseline_images/test_image/image_composite_alpha.svg index 7f1678715ba3..de9e2ca0d936 100644 --- a/lib/matplotlib/tests/baseline_images/test_image/image_composite_alpha.svg +++ b/lib/matplotlib/tests/baseline_images/test_image/image_composite_alpha.svg @@ -1,12 +1,23 @@ - - + + + + + + 2026-02-03T19:56:08.323770 + image/svg+xml + + + Matplotlib v3.11.0.dev1757+g00c32c31d, 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 468 388.8 L 468 43.2 L 122.4 43.2 z -" style="fill:#008000;"/> +" style="fill: #008000"/> - - + + +" 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"/> - + - + - + - + - + - + - + - + - + - + - + @@ -221,8 +232,8 @@ L -4 0 - - + + diff --git a/lib/matplotlib/tests/baseline_images/test_image/image_composite_background.pdf b/lib/matplotlib/tests/baseline_images/test_image/image_composite_background.pdf index 85a87bddb14c..873ee0ef14ce 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_image/image_composite_background.pdf and b/lib/matplotlib/tests/baseline_images/test_image/image_composite_background.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_image/image_composite_background.png b/lib/matplotlib/tests/baseline_images/test_image/image_composite_background.png index e7af5c29dd29..d02f898cbe52 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_image/image_composite_background.png and b/lib/matplotlib/tests/baseline_images/test_image/image_composite_background.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_image/image_composite_background.svg b/lib/matplotlib/tests/baseline_images/test_image/image_composite_background.svg index e63869fd57ff..f0fdcd26b455 100644 --- a/lib/matplotlib/tests/baseline_images/test_image/image_composite_background.svg +++ b/lib/matplotlib/tests/baseline_images/test_image/image_composite_background.svg @@ -1,12 +1,23 @@ - - + + + + + + 2026-01-30T01:57:11.186332 + image/svg+xml + + + Matplotlib v3.11.0.dev1729+g1f7cad29d, 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,64 +35,64 @@ L 342.6048 307.584 L 342.6048 41.472 L 129.7152 41.472 z -" style="fill:#ff0000;fill-opacity:0.5;"/> +" style="fill: #ff0000; fill-opacity: 0.5"/> - - + + - +" style="stroke: #000000; stroke-width: 0.8"/> - + - + - + - + - + - + - + @@ -90,61 +101,61 @@ L 0 3.5 - +" style="stroke: #000000; stroke-width: 0.8"/> - + - + - + - + - + - + - + - + @@ -152,28 +163,28 @@ 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"/> - - + + diff --git a/lib/matplotlib/tests/baseline_images/test_image/imshow_bignumbers.png b/lib/matplotlib/tests/baseline_images/test_image/imshow_bignumbers.png index ec189bb949e5..2b13a801e1c8 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_image/imshow_bignumbers.png and b/lib/matplotlib/tests/baseline_images/test_image/imshow_bignumbers.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_image/imshow_bignumbers_real.png b/lib/matplotlib/tests/baseline_images/test_image/imshow_bignumbers_real.png index ce3404488ebb..8de0a4e34204 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_image/imshow_bignumbers_real.png and b/lib/matplotlib/tests/baseline_images/test_image/imshow_bignumbers_real.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_image/imshow_endianess.png b/lib/matplotlib/tests/baseline_images/test_image/imshow_endianess.png index 28a7ef2a66e9..8d3005b781db 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_image/imshow_endianess.png and b/lib/matplotlib/tests/baseline_images/test_image/imshow_endianess.png differ 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 1d14a9d2f60c..c7683cc6f939 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 9e68784cff4f..292d3b1c0c2a 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 c0385c18467c..977667d6abc2 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 @@ -6,11 +6,11 @@ - 2024-04-23T11:45:45.434641 + 2026-02-08T04:35:48.823370 image/svg+xml - Matplotlib v3.9.0.dev1543+gdd88cca65b.d20240423, https://matplotlib.org/ + Matplotlib v3.11.0.dev1781+g34b8e3347, https://matplotlib.org/ @@ -29,167 +29,167 @@ z " style="fill: #ffffff"/> - + +iVBORw0KGgoAAAANSUhEUgAAAEcAAABGCAYAAACe7Im6AAAMr0lEQVR4nM2caYwcxRXHf1Xdu7OsD1gfYO/aGF/YrA+IUWTJAYdwBJMEECSEhA+RgCj5EIXDBHDAQEJAGEHsEAmRcAgBQkKKAoQQgRFHwMFgCARjbIwPjE/M4nuvObq78qHv6arZWXt3xk9q73TVq/de/esdVbWzFju2j1EYyBIi0yZNvGh4NeN1fL7crGSdfhOvNMi1hIZ37CYtbznZeRUKiRtPGr87+vzUhrk8vWsuUijumvA8c07aHvXNfflm3luwxCh8/qu/4c1z/xi9r93ayq3bLgLgZ2Pf4dIpH0V9e3e0ZsAsKR/MY9t2VDWZgSL3yykA2KUAcd+QrBNtyp/A+u1jAChOSK/CVxtGV1TyxbZ0/0Evx8fbx4FQbBxxQqqvhAKlMj5R6nMqA08l5QJg55VVkXFiroOZE3YhURnwWqd1sLXC2BmTd6bepfCYM2EbABMa96T68iq7MKYQHmzqUUUAxFtbJqWsslC83zuJHcUWTjlmF5cM3UpLmz/JNVvbWLj5MjZsbGX4mE7WXPyHPhXNeP52ujqGMGPaDh6Y+DemnuiHbGHXRJ7rHsGG/FhObNzD6U1+uEqN986csOvIZttP2rp9DBZg51VDqsNC8cjGeXR2DGXWtO1cNW1l1DdMuhSWjmXbizdy/qhfVqVo/JU7Wb7/Udp+dD+jHoi9NNe6hWdXnseqdZMZ3XqAh9qfTtlQTyoE6u1uLxc1WsLzOwsNyB6LzmIuNajbkzR0OQAs3/NX4OE+FS3f/ygATfuy2eNA4RhEQdJTaKRbNfo24PV7MgNNYaqxe7w0AFJ4/HzG2/RMzzGqoZPXPz+Z9sZOALYWWhh37yZO33sLrUMP4U2dUrEs9u6awAUbr2FvbzMTR+zmf4UhfGNnGxaCdSWLKwqr2HP8pzTLAkk7ZJ0B6vT8hcqElVQeN89YHr23P3cHox9uBuBbS97libmPR30Lpi9iOfcYlZx9+d28tmJx9L7oo0v53eK5eA2CzisPsfqiu6K+ZzedFttQZ3BCTOy811CZ871jeeul6wEYe9svUl3L15uBAXh9xa1ADM6H+8ez4sWbADh59rIUb7eX04aUFLUHKgKn02uqmAALLQOXHJssJ5Y7Mj1pEzhWHcAJ87Dd46ZzjiU8Jjy+BLujgebpB1j/zQdpuOkGAJ4aNZdzzrqb1/99CwDKsGVPkgiAP+ucJSxse5oXwjHXwKkvLKZz03Go4wv82nsjtqHO1arTawJA/Hb1JRlL7jn12ehzOQAiYXh/wOlL1pK1C1J9SYBunPFKn3oGkh5aPx8Au9ftI+fUiMo9GOoTUgA9YbXqcpP7nPq5c2iHrKMNIXW5TUhU1nPqBVCv669WPapTOfUEttiOlz54enUCp6T8Y6bpvqeWVFIWUijsvGvX2xYAdLmv3l5s9ziNR0Wcm+yoh23hQtlKCVyVduXZv1rGiPUFdn47x6GrxzE8uIm7/sMfw5zDV3rnmh9w+6wXATi0cxyTn1xK25sl9p3SiKcEntJdtdYenDDE7V4ndufQkNUPLgTg0JPjmLdsIWvxjw9TN9+Z2quIKjZrSf5Jn9zFHfwTgPY/L2PTvb4eXobvXnTtUeHBAPnQc4qu+SZweNsORp1fjN4LB5uOSKl7sDH63PKZk+orlOW+egLV4wSVs+hYlD8hrdwykT0z4wmJnHtESkVzDMjedps1W9ui96JrpZ6CY0dPrang2hRcGzHzH7dllsj14tvb5lzsOY4r6S004joSy/YYPbyL00bu4OKWD5nfVCTXuoXCrom8nW/g+QNzWL2vjY5DQ3FdiW17NOeKNFoxwN2FRkTgIY12GniR8Jz3Ftw7cDOvgua/6p8l7ZJjpQwBsN86lmE7XfbMllzxw5dY2P4qAPOW38TqC+M7mM80e5Jc6xbOBs5mA5DOS+e9cR2vfOcBAJauO5e//P0CRqxTdI6TqDP3ZewAtG2DTWGqsR0nfccvBDTlFXaPh9Vr0eU2ZQYdLqlENfKURDgCO+8iHUkpkF0PMMopX/JD2VaJEBJCoRQcmKboGmdTHOnwypencOy6XgC6Ps0x5+qljF7xFV3to3jmvtP5ydQPjEqe2jCXeYvuZ9i6vew+ezRfdw/hkfVn0OPleGn3DPJtJXY32zhDXJo8EdgwiLOuksJFtD03aY0AAWPbOxiey7O7cxjbN4/moRXfByA/Nc/l177JdXd+wH/yLSx6+Cp+yn+NSqY/sYw/3vcY85s6+dO+WTz28Tzuf+ZSX1ZriRPG72dc+wEOFo9h69ctsRV1BqgYFAHpOZLUU5JMPe5rzhi5mZbmXpq+shn7ToEx7xZQeYvfz36BlradXDj5E9peO1hRSeuKXr43eS1D27axeNa/8LoaGLOqxJhVJRo7bFqHHuSCUZ8wbXgHnmtFj+sEjytx3dr/as9xJI4jsSlJyvPqF50j6HUb2NfdjLIUPccHG0XXY8WWSZw58XMAeluHVFTS3Rpfh7z/xYmwHHpH+blFWdDRM4yVB6fwZe9wvJKETL4RGdtqQW6wnREnPnyvOQPqDDMZq0ukRt7qxhvDqx+6tAneyJt+t0VZtVLVCsu0VVhiUwUSmpdEW2pUSkaW1yTXLCNoqsBr45Tf64oqJ1OhLdPuv2iBr1rukYIfNJS1RSM1IW1rL940/qwEZM6ZQmXbQqN06UNz6tby0g8PrtShtc0gV2ObLRydwRr309oQeoTOCE2boV2PmcGD+5XHqtNl4rWlo2OIOY3ZWmg+mhawjwSsSz3Jl/5MqC8bRMrwyuFoC0ffkXo9whXQVx3DbZAh8vpnQ/Web5QB2MIrQ7JMrjbXJJWJMkMTvEkbM0b0Ywuz+eYbquQcGJq49H6gPKw0blwxZBLA+GCpuEHHanDjfuWsGpAoBWerTFjR/xwi0v8YhMUv2vEmfXUASQZXS7YpJ5nCCcx5LM2UkFPeZQJBW9Kr0DXQFGxvbFkylVKqW0lV9jPkEUG49aeC6dTVw3OcRFiZS6m5XVXYSEdls5L39FNfLUkGX18sS8gJ0oCR6dZNXkD5rrtP8I/QuwaaRJRzvOzyKyEyYZLyEkUqdEg+4RAP7RGi6rxSwTMHm0SUc7Se41tV7hGJLv+j9ENHSYFKHO6FAjwQKrFvirzMVOZ1VvYxi0GiEBNbJr4eXCl0dBTxh0uc5NV5TVnH0ba/CSksUulTeV+GifSEpOe7oPBUHEbC9yjPEigrEXaa07+xpNebPN82WzqalSzzAKHiSSoZn0eko7CK/iOLCuEplBR4OYGTk7iNMUBCxbIiXQmwK2yua07SUSAMOSdabcLEGuQgKVCW8vOLAqsEdq+iocvB6ikhXA9lSdwhDYihNgiJiwDpy5FuOgcpGecgofmrGV1+qgUlco7Sl+2gsgjlh40SAiUVHiJqlyWFlXexu0vIrjw4Ltj+5bTXEHiOBOWF4PiyIE7i4aWW8NCElKqLJ0XgpCpK1BuXcuGpAAw/ofiTExFowgM8D1zP9xzh8whXBfmIaO8T8QNRgkqGleZGoB7lPFxAWxazlSbtOTF4KvjtSRhW0vHzDEKAFfylVOA5BJ6lJGlwgg2WskiV/2jvdBRQHFaOLtaDCQd5QCTAUS4gA29y/Q6vwYLmRoSrUJbw3/H7rQIx0IkNp/D6SL51PT6EnlPK1lg/EZq/8ZCqMAKULXGthriqBX9NK0seykvPMuIB/y7QkHTrWbVCh9EeH4y71bIIVEL4T6MI3oN+Ff8Umq1CWrzyc1w1NtSI4rNV0csYkj4omq0MvcSvPCJOnmGyVire/MlwjE6eZocd6ag9SpHnyFLiG1Wm31cZSEiBcoX/M7n6SgXnqzKvDItTX4Cnh9ScZOAwNm75cZvU3yzH9y66ZfXzRnSHDH6I6EI17EvqIesZIvOh9hSVclEyfAnS9M0P3ak6ubsN+4N81Jfsip5Urx1yUKRsUSzpjdAlScjyhsAkvUUKEAJRJUBG2aa2QabQYWzhZD1HSQE6h6pkaAiSEH4SFgFAoAc6lH+UeQ2ACD0HtwwFITLXnGF7NW3RrjcZaq5hvElupfYakAgwsSka/isNWeb3lSaR8AzhiTQwOv5q2iq1DzJFYaVKZeBE/99MwqMM+Uckkm8GTE/jfuU84Vi9cH17LajkH67E+S1Xa84Jmklod7GmiVU5ngTA1cjWgWviNRaC6nltlS/0X7GBVztRY9XzZaRWptoKmRhfjb7DXQBbOYZfXB3G6qcnOkhgm+yoAFYmNCqCHaeTGJzMADer0A3lZI6OGl4vMi5rW1lbWDHLQFJauwI7MoB6Btl6G8jYnLW3j9IC54rLDIKrzzflYFbi7VduGuRU8H9gIesmPBHE0gAAAABJRU5ErkJggg==" id="imagec211d42f6f" transform="scale(1 -1) translate(0 -50.4)" x="57.6" y="-55.44" width="51.12" height="50.4"/> - + +iVBORw0KGgoAAAANSUhEUgAAAEcAAABGCAYAAACe7Im6AAAIKklEQVR4nNVcS6tcRRD+uk/fXBcigg9Com5U4gtEMWsFQXDjQn+DG5fBrf4ExY0/QdyKgiAu9A+IoCiRmIXRJOALTHIzd+acLhfn1Y+qfsyZGzIFIff2VH/19ddVdfqcmTvq0m+nCQAaBdY0P4yGG1M8CIfRQPBlMERfBlnmEPtqAbfH0TB2+MWSO2m2bnL2J1vOl3qQULgZYwaxoGi+j8H4BgvvBmTPl0Zemvf1OMwW+lvqYNYk5QbQqFmxzfBjA2J9XZTNNN/32RAvSI8b+A5xvEwQ5k/xvEzolx4KOq8jzJpuwJ7RzTFxBYLBkReCE4j1JV/g1PweoyxWlS/xpSa3izmfzC06kAO4paYsQpOIyxg1QlE0nxNawvAWT/PrXG/1hZp9zS17iIZZeBQ4I1TvmxFwqdgZoSUMLXDIiW1WdACOEycYR7IPzviKAu5I7JDDCYhtjuwhH8RVWArCEJqEorzv3Sw2MGaOa94lXQiiYkAusEf0hMR+4/EfWaycfXX5HM8h7DmxA0NS+XX7sPkPLx1ex2OPXhcJ/HHlNL5bP4g/2/t6jBMSexsL181tjDmypwYywlWDIfOQuYE3n/g+S+Dso9dxFtfx+a/P4cr6gQBXaJK5kirI7BI7sod++XFldWwP4lEkzjjK4vw9v1UROX/4Ny6uzvS4ws4nNyfy3V6U0bgLUSi2OepOBWQy/YCApx67VkXk9CPXsPrhxTSux4HfUUnAbcwtK0lsc2RPJXdCEqvWxvKdCDEZlMrWiNdCofp1p1uJWVtTHFi6RJbY7e5gwGAOaeJhLHNhWGBhO+HWbEbSksOuCHG9TRKbv81Y3mdcG9tJam3muIszZzR+l7cj6W2CQIg95Uq+C8vqmKmYnoNzzlkx4qRTX76LT9nt7qBA7BmbE2WpIB6foAeyZbVK9ILUeK2trS+qJDYr4AmU++3uICm2VgSzsXwmSBOVInz888t45+lvi4l88st5rK5lNoG7m67oSbXGtZMQ1xy3JukwmnLGv/n3HN6+egUHZy4XEfni7+ex6fhNUBUlvKssBgC3nUi4ZmPlx6TSpN9v3I+3Lr2OV366iAvPfC3O/+inV/HtP0/ir5v3Rq+lRcmPL+0/K6EHuvHMuo13VAsP5d0FXb15Hz65eR6ffvmS7BvcZYhZmSBYOl5rYTvhxFbPfvbeNCq9UcER4nY+Nc4JLvuWx2N9Wc+6AygAmK6Ty2p8Lt15Y+WinJTYJZmdipWK551zWqas2IVWEJIX2v+/L2JPmSMFloJgGleObznG3Sw2MJxzqOtdoqkq+sEj5A+WN9p9EBvoBTe21aLDPDiDebCigFThm47HQjCxZIztxAYAg86fUbIgnqUrYIZQhdiqypeJFcRjIdwxtyGjZa5W4m6kxkIBy7MnJTZVZFpJa6gR2ygnc4hJRe5XcUxy2DexBzNuq1dLRGHG91fs3oxqC4gL45Sp8X0X2+hBnNxCOdxwCnG7dFJiV+DK8dJim/GRSbRQDjUTWBbL9ZVLokhsiYfALcUhl9lGtXwQ10/e0fSYh5Fz2JqDnNkyRlkr8a5WUuCyjJAw4pe3EXvXmV0ittFh5iCvsGLGZIw48L6I7ZdVVe9IE6pJ57tN7NGM+DZUtm4LbE/FHi0qq1yKbh+Yn7ZU7KqSymL4k+ayktKr6jzCT0mJtTReUSzJMnyN7sBazYJqMmWp2CeW2QyGUS0lHXowAW1HpbYk03788AIfMGPPvvuBHGswMXPciSp8GJBJXS/gnRS7wlQnwDsD8QnZcSjZ0anBB7cppIafnTk1Ytf2tVoruRD5V6sMyfBlRQAIUJYwHQkUQBogrfr/HYHuWKMusKmdpM45uuNvBClHknpxdAvolqA6guoAagBrFKwBrOlF4rInwr7D2cO1kzCeeM7J7tyQMc2G0KwIem2hOwvbaHSHGt09GooUbEN9NlVlhCr23dZK7gyMHtKrhnw/AdAdQR8TmlWHZtVCtRbaaChrhnm670VKxVmQyJRFPanQdMvjuthGdfz9SWpHAUARQbWA3liodv4HoBep1aCGACiQdp7cJkUJOcTOom+lhe2EK3ejNxSPRoTGNHfuXYkA61ytlAI1al6Q7fuQRt+gtysf552CHYky2thOUrhT5rDGiDKBEQCiqWzsgYayqm/AjYIiDFcw50o2YcS44sZo4eXF55x8xfiZEzmkd276e45GAdAgTUN/GRZvyX+8z4kCoX+M8cKryo5SRwt3Bq4ZvXG2lXl/T0x9F5domOv0pEEY5Q3HYkfYzEce5Ev69kpNSZG6fVCdI47/cYVhbsEuBzZyVjrOymyf6ajcd8FH39h2EojtZw4Si9cxUfkeaQi8rdiCr/gnvVsYfyHy46nXXnifxEVKhOSPK0RDObF9Xwm3nENK7JgDPzzyMGhtHJv7SoJUEFYUoSRGbDarSnB5CunMFpqvsLYxY43aOOfocTejKwTDSNw5ZqE1Ytfguti7FhuAQeegLhCFJbTPYiPMnC2CsLuxA1EWiy1xq8hAg/XwNRla6E4Vgae634Eorp242AK2obbjXxU+f6pqCHGC75HYBpt14MAsyBHK/7APR1JoCIzYrNASLnBnxQZgiOs5CTLigtjP7wulOvhmhY4wdih2QRsx1MricMGoEDhHgl2I+Ln9dDZ73LLZPFqXbR1R5iixhJwA4/1YSV8ajwqBSDQ7e3w53/FblBTnK/HwBHFvrlU8HAg6cjOg4N7KOzfwKa24hub4kv+VJIOvMz/XU9yzlxI2yxVwfDbj4QolOA57a2P4AvgfOvJ3K/qieAoAAAAASUVORK5CYII=" id="image83dbd7e4e1" transform="scale(1 -1) translate(0 -50.4)" x="118.8" y="-55.44" width="51.12" height="50.4"/> - + +iVBORw0KGgoAAAANSUhEUgAAAEcAAABGCAYAAACe7Im6AAAGx0lEQVR4nNWcvZLkNBDH/5J7Zgtq7wIg4BVI4A2o4mJehJC6J4AqQhIyXoOY4hmoIiAkIASSO3b3Zm1JBLbH+ujWhz2zrDu5vXar+6+fWrJn1rXqjz8/dphMI7WO8QFAp1Ti48aPOZhYZrwUO+ZOs3MapFgt5O2UpBog/z/2nGgx4yfy/c4lAq13PcwxxXoCrXNLrJeDix1z2ynvkpnTIMXaKW8MyTg75Ugh0Z1dnJ1ySUCH1DcWmcybZMcvjicxzNUxbrkbZluW6wwwksh1ZbEjmWnSyXn94AVoBtRYJPXr2RddekrYwfbfCnsyunOHwNEJE9XKIjZponKOFliuCraUg4M95uBqnYMDP93Zm2lQOvmgcNBVUmwBYCFHC+xVnR3lKMGmd3PnJKuUER93BAfl2rAv3NkcbLqfOicp4hOWijCCzqB2DhvwO2c2vwiTbBSaJuQKB0L3BhvemRMGMCL9tquA0jFQ9gab7u0xW4QTU4yN4rfC7oS8fEe0LWIONp3sgRkSnfCS0EwBvwgbvylWmihz+9/Q2XRvjoFDvEWyyRy++fQnNh4Avv3tyyA2mzfQIHVgvltff/KzqAUAfvj9CyEvD/C8rbKrUeHj7N4cm2FnY30NQmxWjw0boQSbHi2lAbkWb7ATl7sBdvGs8mMrtN0b+YGXmzM9mEMxSBJUgvXgrRSft75bWzpbstrFmo1OJh2QGxhOSPq2Z7QHc9gMW3rE37qtamDTOwZOvvXzQHyLwbfA5qDI270O1IM5NMGmd962ajkPalZuBt8Cm43dCGW2+Hwt5VWvfvmajZAGqhaAG2G3nElNGiph02kotz5wASirYFd01ZVgAwA9mq5CZGjCF3RN4vcAm3rDfFMvzL4J4EbYLaC4Wq052AO5NwvNlo6QBMlCW2K3AbhEZwMAGaZzFkHjvybw1UO5FuxrdXYcT8OQPrewE20QJE90/HcvsM+dIxWWiuDsV15sfY49wCZnxpBkqEp+CASFzvqDdk+wyQ6aDQidS7IgrQjQNcTm67EpmFpyjnWwAYBgwhE1E+JV+gALghpgq6ZYplZUj03h+/wDGQNztxJXI+eLAdZ3Tw62a+i0mqOhBTYpr3Mc04rcf0WfFLA32JORf9SrLVAY/35hj0ZqqBCe8bvMKu0dNukJDjtJaaBif/RyCAFCjnQ8r0GtzCvXy8Om+VN8PNbFkRWFkxwNt2mo3Pj1nbKls0kNfBG+IyoESV1VClitIX/+FDs7oyO4W0mF6zpCypFeXgP70p1dA5t03DkoE1aMT86RFv5fYU/+Gtjhtmo6O/KCWtr5ucGejbK/l2tZpYbCzx32bMm2KrXo+sL8sOcMe9lWUns1PY/wQ3LiL13vkrBJG7DWMqGWTrna5K8Am9TgsgFjMiHbhbbac+00sXP8gSr+MmCzoH3ATp+Qr1AkHr8X2OHdqiCyWMQt/qDYE8H+9cfXTMLFPvvq+/p6AEgb/rOJqxSpLNA9OujeoTs52IOCuVEwRwVLackW2M2Hb8FaGgHIPOfUrlz36HD41+Dwtkf35gTz8gb9iwNw2yWx9XlVk4ZaU9L5KmgjPd2tWqHMl7qTw+FND/r7LfDXP6CPPgDcSzhScF23Mm/9mdTSPcnnyML5Q8rwn09qV1QPDnqwUIOB7QfowUD3BsoAysTf2Ep5hUvMQdsCO7a5EWrPVtK9S73JwBkKs6LOwR47qBfvQbsPYW/fhz12gHOLGFFQaft4vynYAGU27rEl14HnzmGNgRLvceUAe9DA7Q3coYM90ggHCOEEghjY0sJo4XLraRzpqYEddk4SEF5zikmmFexRwx41cBu+tps+ffMdmL2lx6ud6cCS6d41wVavPv9uUcr8fq/0gLU+lg/lXnmQb+n8hTWf0sPxo5OU8b7QCV9XmMbWrLJwJmVgiytnXH2ssN2D+BWw53mQ7sNvu0or6l9eOkW49e4YNgCQ6k1+4CyInWhcpGGVnjlsACAMNhEZDGQEScW5InuGTar3Hhv9/VmCIh6GzESlv/+wNa+f+9KwARCMlzW5bdaLZwXNsDfkfVLY0TYLO2dFEXY1LgBlM2xJW64Do7yEx94L4F5kqi983vcXgOLb1WHHuefnHDf4Xu9n4f1T1SJo57AJ/WMUwEzIAxW+7MOJFA4EBjYLWsoLPC1sAOTmbdUgSJwU+w6/8Ia8Tp46MhNlbklP0NnkuAO5UDiYFFdAEiTEshMS3+GXYSfaNsIm52+rQjIXuBrE7xE2or/ZFf8hnrMgF71toDScdZFLjoWNH9snKFE9pRRgo/EzAE5bHDvF83mZr2a04vVO9h+iyFFqJHx9PAAAAABJRU5ErkJggg==" id="imagedb195dd65f" transform="scale(1 -1) translate(0 -50.4)" x="180" y="-55.44" width="51.12" height="50.4"/> - + +iVBORw0KGgoAAAANSUhEUgAAAEcAAABGCAYAAACe7Im6AAAMKElEQVR4nM2cW4xkR3nHf1Wnunume27r2Y335vV4vPgSTOwHIsWOsROw5exLkCMhHhAClAdHRFEUO04UBIqEkhgSYucpspGIlIcgESkOyLLAimyQEIGsAWFksBexa+/Ve53d2XH3dJ9zqioP5zKnz6nq6Z7d6fYnjba7zne++upf/+9yLr3i1MndFocEQlTGpEsRCHDoOs736nosu31w60qH3UB4dPf82jleFhVVJklE237MAkBXJk8cMvTrSuf5Hl0h0BXLib5JVef3nRq0husu+p2DAKiu7Uc8wEmkyn61ZHLeJS24YJqcjBZpmwYt2WNXcJWl2hWawtJKd69rjdOum0lQ3rRxSs8ms6uOUfmgFFVgMrDaNtFriZhdgWFRTtOzMd8Pd/K10/fzxpH91FYC4nnD/oPn+ezS93ikeZrF/We4dGovHdtvIwe7zDB3NI5VujYGQLVtbWPUupkjhaFjGukXaJoeTRHSsZq2qbMe1xBaIGMgFnRjxVUzTdsammdupq17tK2iY2q5DfCw1IL0sHdcsmaSMFftbNFAIDzUt5ZdQRuAC7rFf6x9gNfX9rLcvMiX7nmeT/Mj53lPvX6I167u5+65U3yweYwltQqQAyUd8/nCepzSsYIAi+rYBpLUyZJf3ZRVS7WL3KwEc/tO8cLRu/jaT36XPS/V+Olv3c6XeN47ybOHH2TvS4of/t77+Pzvr/Hw8hEAfnZ8P11bo23qtESY67vBcm/YdkrXBgCorqlVDmZOZqHUMQ2mRRKHXVOncbLOwnePEk/dOnCSqZN15l85wrv7bmflgVY+HmA5r2cJsASysCOFj3ICoGSShb/46pv3W184lR3smAZN2WMu6LIruErbNPhxZ5mXz9/O22cXMVdriFbM/t2X+cjuI9w/c4SWCOnaGuf1bH6+L3RcfkgMjx78+TUudzR5+dhtAKiurVXCKaPyLrWWKK/+Jt85fDfTpwM+96lv8EcHf5brHuKXFePHgR8AtlSmb/vi00Tzhgd+5xd8fOdhANqmkYdv0Y9JhFMmbVsHQHVMPR8s7mhT9mjJHgA/v7SXm75tmf6fn9D+ZIOtyvK//pp4eQ+vHjjAx3cepil7XNIz9BWFCYKSSTfFRPUcOSeT8/EsgbDMNbqcuEuxo3YPHXNuy5OuPLzM+qJkoXmWS3omTcoNOqbubz49Ib+d0s7A6eh634GsEbwcNTm8uoTE8uyt/8nSU2cBeOILH0MUFlIOnaIU9Q48908cf+wvky//Ap/98SdY1zX2T1+mKcO+8/oAmUBlzwpRHlZlOr/Tnef/3lgGYOm+s/n4G1d3b2nCE489CfxV/v3lt2+ju9rg3juP8r6Z88Dkm79MshyowsLlQyYSy7n1WeZer1MmxokrC9fFgfitGeZPCo7tXeSm5mWnzqTyT0enzFnX/Tkn2712WKd5rupcpz11XRyoXxbMntashjUqPjiu8cYpWTSpdV3z0jma2b6rQNOAcCa5yFpP856zQ54AUL00mlSog8pBKSz1QLPiaICbre51caC7J0JPKRYbIT2T+bDhyyRAySTbLNUtULpI54aKmbrjCgD/+9Yt3HfLWwDc0Fzf0oR3Pf4MPL3xffeBFdo31tnVbOPzYVKSMycyVeZAknvu2JlUkU+9+hmm/3yGueMxNzSO95VvMaDCFPW+/ie/zb2nv0J3QbD6YJf7Fo4C0NU1up7QnkSPA+Q5UPZiRfEv0gFRGmpTQcRUEBGuTLHvv97mB998kq6uVrdh5FDrDD/6xhPs+rdX4UyS1KeDiFArQh3Q1Sr/C01AaIIcuHFL5keec0SJzj0dAHPJsVbMhYdu5sPH/oHeFsH573dv5sFHvszVHR9EL0ZcCZuEKqSrVW7zvRBSAGEeVrp6hz69PczKehOA2bl1Oh/tcTWWzIV1Pvfaoxyae41X15e5/evPcOTv/qJi457HnuZv//QPeXT+p7z07vt58cQHuPBYkq92NiIurTe5RGLfVRMnCVQvzsHpzzlFR2MtEQLmm+vcvXiGG2ptXjz+fr7/+Xt56oXn+YNbn+TNo48Dj1dneA4eOPVlvvjit/jwh/6ed/7M8Mk7D3MxmuHN1Rs5tzYLQE1VnzxMmkEZk5V2MCeT7FikA86uJ4uJjSScTcf3LMBR/yThbAJ8NFdD64iT3R2ERrEWNqhsigcQ3/h2SmSS9ak4rlarskOdXp234kVOrS0QxwHn7rXcsfQMb89N89Szh/ibu75dsfHKsds49wLcsfwMJ28MqKkur6/sSRJ+Yc7YuDdnEqBkEsYO5rhiXwhLqCUhCq0lQWBo3bRGbUkTGMmzhx/k37/wMLMnLI1VTTgjWTsg6bwSMbe0Sv2gRsUBvVBxfiVJ8IHSeB6IThSUTOIUE2X1hpd9bon+DzaWICxGSLSW1FVyT5lQ0liBhV+1UScvYnbOE08t0L0xIL5BUlcaKSw6DjBagADhCuUclEIPNaFnWFnIKxP3O+p1KHXexIKQGmEv7T8ahtU7NZ19LUQ8g1WWuGUxLU3Y29AzRuTr7mOrk66TZU+cYqLIwEkdcrOn/4suFRjb0oQzcXWSXrDp4gexdVJi0s0TB577x+o2+XbOuVDPDC4bXt0hz2dEpjl0vTnNoauE7h+1wlY1RwHFpbAlsMs+DGL2EP6mNsq3dQelEVV++0P0Lcp1ks9Ydcg6kuzIdl0KW2Z21cagNKJE7OLekBMDdgDNxbWA8h4AW8kCOIMW6rVbGN44fxP2DRifONiFj6p4y6RvoT6rAxwqHxoabI8NJ9ib+FCWawFbiXIFHujkEA75WLWZwpZ92CawScNqs4mHY4TPxiAnh7Ah4OhfP+E5YXvkln/+CgBK6EGLdyPsKwBuG0Xd6wD2GCQrUv1hNVLuGDw2Cp2HAXucInXis/Lew950oUPItYI9aHwbJYsmJUsJebN84NxlWx2r2LkeYI9JvGFV8dXZH5RUbPW48IFUsjvMfOMWkV41KFm9hQsMpv7QzecWwR6kOw7JMFEiLm27s/QO8FIkf1YUzjVuNjnPpYrBpEMti6Yk52xSjgc+1ZRgAwEyDSebAmNAmELrl4fZJkCX3ZkAUBvMcYXVJo1bxooMjOTHHiIHQGibZPysEhbyjxPoUUJtDNLPnHzUrWw9uUMAaJARlN9Pyxc34GrhmvLaNkohIbtDxvoWJZLQsMn9dmRsUV1L0DXI2GICgZ6SxNMCXdvQ63vN71o77W0WmeZhb5/j9UmACWySRCwEPUtjJaJ+oY1Y72Gn6kSLLbq76pggoZcwaajZ/jn8gGwcmASD5EZYFW52l8On1OQJmybgjDnGEvQsarUH5y6ir6wiZ1ootYdgViGmBVYKhLHIOE3QQvRVtqIMUwDGIWIjIXvu3FQSRPavyJ9lC5NQUEQawggbx9goRkQaGdt0ByxSg9Q2DS1bAcfL1gk9uNoIq8jd5/gkL9dZL2MtthEgd8wT1GuIqSlMI3lWFUQJIMIk4OQMLJV1f6M4GQb1hZW/Q3UcEOmCJYj0V3a6WYffWEBEsxgl0dM1rAAZ2vx1lmLPAwkwlfBxhZr/PYdtk6xIKRE73kH37JyzpEuBng7Q044XL6PSJX9fou3PdRXJeyaX+9srhbAqLMCxS5tVDivBKpFUJgnYpDLJOAkjYW1iQ4CvF+qzKyeTZ4qSN4FCF8Ap7lKeD6o73NchC4G1AoxIFmayPGPJkr1IfrhZuXRwFgBdnG/iCbmf+t78Iyv36hGkuUMnvw/vA6/0q1+MI8f45nNszDglw0SJKKGLt3Lkv8IvjA3TpKU6g8Aun7/BlEn3OSlziBOURgWlIrZUp4VIK9Lwdt8zfc4Gc0rXD1lC3AyU8phNLynSXe8DppjohwF7Um8tZdOn0aQqL9uMCopnXIAb6BHtTiIpZ0WqypxRdtMxZouleKssmfCF5wZzwgikpw0dhTlZgr0GlmwK9phEpHlY2VhT4b3HITESWK6XIkdgjw/scUgaTeKRHX/seO3NsTDfDjoB8zDRYcMJuM/udWT4MLrKho7/qWYENngX53Sier716vpAGx/wyob9P1sedXJnuzZKSF0r4OBlutM3L9Or8atsXH1FdthQsYAYkiHA0LubsGmMAHv8FdWLILc8JD7mOHv4nXSC6HNslIIwSh7y6PqA/H9WMNcW9RCPZAAAAABJRU5ErkJggg==" id="image5d806340be" transform="scale(1 -1) translate(0 -50.4)" x="241.2" y="-55.44" width="51.12" height="50.4"/> - + +iVBORw0KGgoAAAANSUhEUgAAAEcAAABGCAYAAACe7Im6AAAIJUlEQVR4nN1cO48kRRL+Iiu7Z3ZZFqRFJ5B4SQgwMBBC553OQgjpJAwkdMb9AfAQAouHhYUBfwMXc5HuDISHy50DujseEsYdAwLNTE91VQVGPTofEZmV/RhmCWm1M9mRX3z5ZURkdXX10Nff3s8AUCG2ikgYBYzkC8VXwFB9BWSdg8QCMAJ2RYrvA1+J46PRfwZxNuCy/R7Ek4TrMeRV2xX7Eypw5GQArCNAYM2ybxQcwFrArYiAAEOK1WMQgM73JcI6hh182wBXFqAiQsNtNG5gYM866wQTFiAsCgCMIEwlzNcwDBAJ02OIEDCSuHI4MZsMYgF6DMF3mG9PebEZ5YQY1EVjkS8r45CF1zAkIQBZfJEvs5zBovCC7wBpT7sjZ3JCAIeDJFTvO2++hqFtjMxhfpYCg+DBSzmx7SrInM3EgoVKoqgC3jli2zMnc7wgrlBqmseEJqGiXbqaYk8YwhLtaXfkB3aDaGAUE9ICH1rsFx/7QsTK2Sf/flLmEJVVGFhdaO/4ypOfTmPP/ek9/P2zt0R/dq4rPvryWay6hei3q9jb2GlQMdLG2LNu6RCSThk5q0bThAmtAsMtYa/GZ4hSCaJomT3HRi6pzLYXym7qx+l2hP7X3O0TirI1cwkxQ8ASmw6ihNj2rF16A+oR6ZB84fE3cfvL94dflas2AOREfu38r7i1ONVxPQ5ytmoCbmPeJYwi9lRWuT7j2ihMif3juyfwl0f+uSEklklJo95NqLNumW0jtnbePuSCa0fkHPvlq3tx/uBCFFu/oJOyaj/ZE7YTab32vM077YPQ8sTEhBSx5fd4u/cZ18Z2klqbPW8Xaorua5cAoD1mjBuh4YpXuZrvjmV1oVWME8/WbXynpiT159r6oVok5Auy4SKJsqsgrp13wUEkldXKKatDiDLagw+cYNVKt0f8zRE5HCCz51SMXXfSPT6dEBF7xzcldtP1e+n6K1i1ckPW4pX0pFK7aKUs9nHtRWOTDqPRHgithRKWcEuyaluTs9i3qedoi5cmfv7fh/HHR78pIvNLfeztVlqU/Piu/UfLYq8hr1vhRrRy0Tsu6NV//Q3L2/1tx6frt/HTyQ3QyQL2lNAdAc2tNe6+dYo/X39jmntRV3pWKgs4ZA8M24kkNj318TveqES0pNT0DJw3X4tX5Ct6lottWyFzNoT6/1tvrEAoBbdk8ZcpdtSQm2Zek1Q+HirKnqsqttZGvMyRfNRTahp3jvUiAUt8fxuxLbcbF7n5+BDiogoa7Z0ktu0aIzrEM3vAeQKW+OqxomFp7EBiA4DFKE5yQcEvmQVxjpDnK4W4GmJbTGXlLl7bjdRY8GLBgi5TbCrwtdT60Zg4ZpsURbNLEHtym8F3wAhv66baiA0/X6ds+Whg8RALdV+MKzlsJbaMkWojlhrpampmYADCQwpTYNpFlCsgtjWOOKmFqrjO8GZ+JvsS47+52M6P1r1l4i1UQ00QCl+aLbaCIYqd4RDaLmJbakpIziCkZVXOIcNB5XEosTGUlb74UeE5JDWMFMkZGAcSO7dmALDUphYvK6wdADJGHHjfYm+DEc0XJvlltafA4cuHFluPl8dNrdkmP8QsScfCwJpvGYfDim1N0JBz/WD7wPK0qyx2VFYR5hYClJCfE69kw/YptjXy47m77752iOy4+Fy8fYptqeGkQw+moLm+mR3bV6aF/l98+Ho6sGJPvfGBHmuwvudkSif6VJMCHwLYDDXJ/T9i3pyPtIXYQiyJ27ZGrQLvDFiSyqpgR4kANugf1R/EIQakU3CO2KlYKW6lNucg8k+rbAblfcVAl907ZtjUThJtxJpWfm/iBpZEGQGoY9gLRrVimIbBFdAcGzTHBK42b02mp2kLGnXoH/rukj3SQRRmtnqdk9o5JgCmB6hqxtGPDZY/nINOV+CjJdb3Xcfq1gLNNQBEXv+ZlxEkv7xrLblQM94ZWOOcVjzzOocNTQ+hVTVj8XMN+v7/6E5+grl5Awt7P5q7KrTLCjAM6oQelCiTOT3J9d/GtIPIxbTUKnduUjvK3H/rjftSQtMBTQNe1+B6DVq3oBZDmVEvTqulTipbY2e1LAstbCcSB2vCr7olRBl3lCuAu+G47gA+qkD33ERlLejaMdpri0E4gLshc4Qvnm1wA5sG/KxW6G1lYztJ4XqZE1kgyghGLaYOSwy0xxb4w01Qc6MvuWv9czhV3W0EYBcjxtUYslFe2vk6R7h9EmDGmeM5xakXcTKEbmnQLZfhK6CGfQEUUZInUniq7Cl1jPLOwDVr1k6nFJ5GUU+OcaBlsAmO7Zb7w4wDX6FMIlzlkQdZwO2VmpIi0UYstY44/uMKw9zMLhOBDIM7AjvH9tiPQsGzfaaNyy/yVzK7xMR2EqzZzxwkjkcTL6onzy5ubKPgRSWl9CT9Oati89qJIjY9/8y7DCTeDEqE9McVoqGc2L6vhjufQ1H5ZdqIRdPJ8ceJQqnNCa6WTwK39DrHx9UyG4jKbyphiYP76cM6eP8w7mhOFHXnhIW6O3SniA3Aog3Oyi1FEQlJQhfi7iz2triQMqcwiLgbexBF3X1D83A1bqkMDHAtaufPahihQxUEnup+D6K4xm7zPkRmK7iWG3fU+Vm5GKMSQldd7AyuxboOHJSLiQHQf9hHIyR0yTtNbACWa+Gv1RSQExfnEPGd4/ms+uaEz8TSuBWszXJdx46Z7Nk22GWKDgS9KsdBwLDczDytNBLqV2x222GWxNW4Kb6iwAV8CdKfP3LsOXpZAZu/g1Sw2xJ5PYsOm52/Ajp+c4E5ymOvAAAAAElFTkSuQmCC" id="image7ce36a921e" transform="scale(1 -1) translate(0 -50.4)" x="302.4" y="-55.44" width="51.12" height="50.4"/> - + +iVBORw0KGgoAAAANSUhEUgAAAEcAAABGCAYAAACe7Im6AAAJDklEQVR4nM1cTY8kRxF9UZXduzOzKzAslsEW+BOMwBLiyAktshAXxE9ByBwR/2AlLvwCH7gicUA+IBAHEBcEshCfu4C9XoxXtnfxznRPVVZwqK6erMwXVZndPYY49WRFvoh8GREZVV09cueNJxREKjJWM0UAtUjW/B6D6JL5pq6BzH3guhXBrSXVdZ5SA5D5tmgK4sEdBoiuKiWCDXl4rqvp4jy84UOq26lPSHOrALSWC8ebYA01WVAI3wSf6wC/2ZBmR1GIoZv5AUBAOrM3Jqm70A0wxusYxn2Aa3kHuFN1F4snUVRJOsjI6g1pglGT+RZGBdAorMnm90tivqXDPHoGDD8aCwl3K3V0IiOskm5ad2Z+j1FIdoRRTDaQEG6TPTbmVrqgxqhBnSIoHaeLNjAsgrgPJQRxHxjJ8Xz3qLuSKk1FSBwJhBQLw44mplvgwyVFtDsl5BQZM3QrLSCN1BmAE0/tqRE1JVFOfOBppbZzvYMXQCeyxrGscVKtcSINKlG840/wqLuC0+5K6qCxkB43tZf4oGPdbz73OsWak9duf477ENacMK2sUE5CTnvdT7iHeHH5Dl789L1kznt3n8Rv1yf4t7+OVcfrWpL3w8KJH4zkfWRYtxX5AOBOu+VoIDc8n17ex7ee/70J/NiTd3ETwM9ufxZ/725QguzCmplOe8hQTqzUAwB36qcZZNX+xuI/+Mbxe1lO3Hz2z/jRX67j3fbaGFe6tB+xjnSqux9Zj7or/ZpZb7fBduvuos8xdzIi7rnl2zj61D+yHbl59BZebR7DsBEWbu/D/gU3R1a62JYHS0bkpMaZQ4qXn/1TkSOPP3UPx398Afeb6wACUpK2oOA03LPorI06GBLuzqKac0gHQjmp1jjzi40DVvqwviTzkCiUuNZuccPTanCYXTyUI4PEu2WlRQlJu8qpX5q2BnFrn6aVvbO7O/huew1nfmGk6nw/tdU9UDTH5YR2yKsicvrxf735STzxVNrbTMk/1x/7vyInLicM163itMqoB68+eAmvIJ+cn/7t87j79kdx3qXPEnvcmowRHw5YA8NyYuG6hjlsKMvG6d88eBo/v/M8vvrMX7Mc+cn7X8LD5mqPXRANLKIOVv98Rlqt2/k+R6Lx99dH+OG9m8AdTBJ09tZn8N17X8HtD26g8ekmxLipD7UxPu1vjgzlZLIgnwdOM2cZwLmvsfYOt974Ol773Uv49sd/jcejGvSDP3wNv7j9Ah6cH2HdOupErr25Rewiq5m2AgBc440n9Ox5d/iM2dd4dL7E/bMT/Or+M/jIL89w7BqsvMPKL/DwzatouhSbkmQ4d5kk5ZQT+eKPv2day00za6zHyJtfas/ULcCdi2bXGpHTK1rj+eSUOPthkp6j69qWf1VnLrbAKb7Y+Hn/lO7/lnDXTUaOkXHJuBRGWYlu3ib5Sd3dSHadn/qmcXyNLqrA8DzZEugaqpdMdhjVrmvTyLGMKQM2lLkTQlmz7DHiTVXq236lwWEgJ7g4UhPjj5ko0rkoKLAnRbrEVmSPTg/HN7oO27QSE8RGIQNkvpq6Fu4YQydqWqrLbPWDcXRM6QKAk6Dm9GmT97aD/RYGm2/VGmssvcBT2rLHzTEMk3QADu1c+BszC3R1X1zrwmWRvlF14qORAqfpooHEaSnEzd/5QlxL0SBolFbpvOn6E84cEyWpgoHB53MfZAfcPKI5rqtYuzoAWMgFxzFPKePkoKTl+1CS1naBv/jopLUV7MiYcciKrjmFnX3YJcLnfbDTKqkbuxM0i5GJK7C+Is+vPdPrGKO7ikSOGjFecppZ6cSGSwhjPpRuGvWBTKBpdWkO0L59sg/L8oG1/kX+gq/ZmV9FkXCcNJqJsU9q2j6kDLPSMshOaZUT3lNGczCyCiwTgnGQQ4K1IBsZpZV1nB7iiNz1OJ2cX6BryeTtw2Sfc4goYP3VDlEXXyrRLUuti49OWjUB+hphbRkZ2jHVxvYydTdjr9/6Djc6I1945dYsQbORI3FnMROyI4PmrtuE77XrBSJ+3hbvkAfdkuNUI51Bj2Lk302X1JoSYf1dbG/cBGY4NzqthozsAOkUor2+VgKtgh+ylLQFhziSM8QqJ+GfriK/KQqNTh7d2hNTtYq6UUgLaA10C4FfCLTuiWK4W8g96tQ+wROXE2Zv4vaBSOzcQMxZh8UHLarGQ+sKzTUHOa7hlwKtdDuvpC8puhvfQeJyQjvkqh1HjqbNpt1yd0C97rA4beEenEFWDbBwAI6gtQBSoat7yyW4uxwEpbINiqk+R6K0spu49IJ02lf9ViFtB2k9VATiO4hXiFdUEOhww1lUcNMLhzilBgnLiYXrqvCnbBPhnewkAFEFVPsivOzfd9FFDa0Eon3KdbBri4pMRNPuEZ0jQ+RQ3A32KK2KWvzhugLqKvjjBeSqg4qgW26+f++AqtHJ9GFOUV32rfVe5Kjtw0YuOuTQ1mjCDHnan0jdsoaoQkX6E6pTVPGciOkhGrNOJs/Hd5WhnEydxq7y9uuzcZ2Za762KagKdD1BQog2eyfyXol9wu3HUnwQXdi7wHXS2OTILkUxrlEkHexjmvVc+bolIgY5oTlXEXJMAipSQBEvIOo843s3kaS4T0VHchDYb8wUyeggAuhBIC9/+fv2FtAiaO1kQZTR9Clo+gxdXrsM3IyIdtKQ2/JhYnipiJQhtfJx02O6nOxR/G7nGxFCfYgek0pD7h/4C3fcMf6yC9fdMxJLcK1GsiTqHDyJnO2xuZvjo6sh0TvgqokbfN4Td4Qd4I4j5wC7qHHU+f1wzWO+FBcG0XHUBbHicB78m47KOAoKyJFLSsmE9B1wqTbD3YjTpgGEVUp7Mut/TKcOQDgqgZCWYF9crYS2GoM4tGFaGQuJCNJJ50icGrtDSTYJYlW3ZJPIJk+lJgCn7LSynIGxICs0Gdnhk8Ex8Mz8YIs/pGh2GtacCeODA0nHWJI2l0g47WRNwvPKh9M2IGcwHM0diux4pwfd6PZjalfjtqEi/yKIvS0d6CL6IawIqRuTxBr+kltMN/rHO+p5T+DBawnI6UR0dXsTGut6Ox3iKPOGb1RXDVxS/+jaevkv25GL1SErJjAAAAAASUVORK5CYII=" id="image581ff47e8e" transform="scale(1 -1) translate(0 -50.4)" x="363.6" y="-55.44" width="51.12" height="50.4"/> - + +iVBORw0KGgoAAAANSUhEUgAAAEcAAABHCAYAAABVsFofAAAIFklEQVR4nO1cu64kNRA97vbcu6vloU0Q/4CE+ARAxPwGOYiYAIlsf4C/IEXiG5AISAnISJDYvY+dbtsEPd3jxynb1TO7EhJOdqf6+Lh8XFV29/Rc88efHwac2oCyjcQGAKMxhY31XzgIlvSXsAt3yc58kLCDwDsayWt5Pv83ANaF8wcXXRhPQvvIFivpwrnjGl1x/4XDnDjO2JUj7r9wEGwUGS5i37ARRRwtCXbzgWN9cJE9jS57H8rEGRCAUJgxmtI4MiBOIuQC8Mg+uZqLVZhOHFI650tzEjHj0KSzfelvoo6+ACSTD4J968+F2rDJSgvY1gKEMgrT/vEngk3mwNu6APYxHBjXQkjFKm2LnUdbU/AGR1PwGHvliN/EGcikWahNGCmxIyIAwBhKDi84RjkCnwjjkIT0obRP4NEbC2nv/C0B+M2xZHAmoICVV+YyDhaJC9Zf2L9MfXsX1ZzF0TI0WSoNigmNgkObUDlHLRU1C8Y4OkrH2uxjOIszwmOiOUvECYIDLL8FrFSPun2AByurbDFHBEzMB8E3ALCv/YFeSBxPKjyLImlHa6xqsnsxbD+v7O+OSD41+9I96euo2HWaed3klXyQdq7+1GouQtTslB0C188FeRiL6r6GKcUS5yYQgU7jxfjplCollou57KCpfdp42TaeY/k87H1ekBWrNhqP7z/+ieIB4LvfvkywlFcT9qa+I3370c+iLy9+/6Idudll++B4zVk6tlWvtVz4Wn9JfIpVCLr54tIjCy3weeQ8uHICDLgQ8tyUGhNeUzck7J5FixdqJBEIlKLb157fYbDnHNL9kNRee0t4pcNh/4FtjzixL70LZB9PqyuukrgjtYV6cAelGP27nzaK2UK1RN7EKToqVlJqjFuzCAwrHhMaYt3N55rTuwj26MYMUHEyDE1s3I4uDuUdNSUwW+bDCTRXPVkih93ypAfNlNt8+svX1EMqkpBipiboBXY5pS8r3r3Rax/n8+qyZ2SaQZlIGgfflPi6VI5qzjEWR0GswlKrbiIXC6QQfm12dlmeqSKif7BLI+1tCwwAdvZtcSTnNE68TYE1EVwVZ5rG0yD9TvLJ8wFUHIKTmqjjYvX7Fo9lvZdcAgyZ8ULI7P0OaDiMCfDkOfR1xK3zWh+fcxQD0pWjq2mUUdk/Xi+Hq2LlOVs/m9TcPRGOZc4txv460C+8thz0YwHAhpl8NZaRbN07a02g2JqYGiwzXii8wGHh5JpTspuCJFSxEm/OwVOPY9l4y4d84kHyC30iW1QKMu/MYpNhFRzlV9oRR46VUyZ0Ytl4rEBbM5WOcifLi4HWBu6PxMGxNV7ub9Jj698/NyajNS6fMEElA0YmcWuROEoTFVhKye4gL9O/yiHwWuNKHBdI5zDl+E8JDFgzl2jJ394JyxxG4Oi0CT7Ixbyfl9ntMMuDiilLdgrxtMA4NOIDyQJUfdCIJNhjji2tDLnYHMDwy1fhYKAGb58P9U0k7nYuyBcOEIQB9qYM5QjEFtmly+KHZuSwh6/SQpGYDk1s2y5z1Bch5+3xoRXVSeQMrtGpQhT/d28q1TlKj2vHJVXNkvC05uxQuWmT2hUKp24n4hdacy7OOZrOzfFVRwIO10T0NRY4Tav8dafGhJrFVrBrCvalxX3vWHmzwxwiIDthCbzKVcovXVpYRQ5NBArY1WiT78cU9yNMSCNgmc2g7+B5Hk+AXlyPBDuyrbxrxTZb9uxkx0T7otHs8K0cb080brcPrY6a84EG2xYwdGPZWKVv0o5SYu3g8gjQbXsmAMYHGLf8P5jlNT8/lsuliS5NZKy+/frjNwIZ8MlXL07Y/oNlceNp8lvIRqgODrCPHvbeY5g8/GHA9GzE/GQRKRb7mruU6tAKgB5ZGpGc7FbNwUn4jEeP278nHP56BXP/iPDsKYYP3gGeH+BuDbpqU+FhzQfeXUyX1c/oyNLrg5XeR+K7SSgIhilgeJhh/rlDePkKxnmM7z3BMFv4A9/Reietw8pfsQBL6qe88rFlbXYg79Kv7/B0VXiP5fvWgwVubwE7IhgD4wJYVGp3jhJbgntSbE/5SMTZcwo2PsAfRrjn72J49hTuxiLcDMsL1ccgcgTp16XNM5XMWWvSQnEflgt2mPwGLLDCN/BJ4QyAvxkQ3r+FCTcIxiCMZtnBQuVkWimQXZO4ijhSai1Y89nnP4jJ2joTpFjJK0UaaFLmGsW6EdHWTFlFTrbeSDeSBl3b9Okc1belr1hyuSOKz51SoyG8fPw0ou1wdOLgmtOk9MyE1zGBV6hDix/tAnr2t/+sVvpwNtpti1v/FSKnnTb87p5HSXxLECHi32gk0VP6lvrA/G2nUlLmV3y05VszyY8CxUkmv75fAZIgijoycA4aEUnqMh86Fpbh05qTf+XZcjBq4nbMdoH9vBuiwRu33rpVO1lbTNltuXjjySYhhW7/JKh9p+hddia8gLVmliMnboEJ4QS8wmFzBV764E0SWHrlhnBY5OIANCKM5LBA3C3yFXgNUPqs5GUC23A8Zv0UkTCwV+bqDqWF/c2kJY3yBi/rYTHPCbA4LkvxORjApwdI+XUSVlcG0N+PqRaHY+mThh3RXRZkUXUiUoY9v6/HHMn7y4dPtci92E6R11NykVbqAQW8OEG62mn/TWQhMrhv7cWr+sZqjj+yP2KwjtcTAdyRIDkB9NcqYZF0x4qSI0j4jNfCy98HB3q0dHRAQ7BBcC7/i0xn3/Idx1GBFl6yO1ExPRfToRnF//9Bs0r7F+A3O0N4Le12AAAAAElFTkSuQmCC" id="image6f0f0bd935" transform="scale(1 -1) translate(0 -51.12)" x="57.6" y="-149.04" width="51.12" height="51.12"/> - + +iVBORw0KGgoAAAANSUhEUgAAAEcAAABHCAYAAABVsFofAAAI8klEQVR4nO1cPY8ktxF97Obu3con2BJgSID/gQLDgX+APwArUeRUiQMHhkMpcWwBzgQoEiDn/gEODdj/wIEAA04dKJdwWp3u1D1NOujuGZL1Hpu8WSkSk92pfnxVLFYV2Rzuuv999mbE1gbYNhIZAIzOGRnrv3JYLAAMhENiCTuzQWEHwTs6ZbUez/cNgE8/hOLhAGAhnUYAS4y5zDnT/8JRYLdZDAXH4JzGFuwDBmqDwoaCd5U7LDHHppHk78NYdLAkaycrH1NsYihNzyyqBTahY+mcp9Fl2s4cWf+XTOWEw9+HW0FSxEEsnJGRk1kpnRkr2NYJqXKULW4cVqxrY978i3izkhtnjBiL8JzPJNHImTMVVjm5dOgsJmRGu0MVB2CdNCN3pp/i5q/t55gMck58mSrYnZQ5JKbYYLCsv+W4/LrjU2yLU2cyKZk+wjGS/gDg78MdVbh2siV2oGWXY3n4X9cfUCkveGnKt0W5vw+PL52IAlaIVyLiODJAZeDKbaOmyXmV2qM4WH/FsY/DL/GSeQsGM4NBGL1gNI7buUrHBXDHLXE0xjEbVjsGYsPaSvwCyxs224zjhF0A4L8Oj8xDHQEsWtgS3546OlLaeZm9TdEKVCPWP13utoesUF1vzPVYZUN7ustxHJQRP0f+9jQgonx2VhLtxnEG2rAuWOzuLIodBRYGC4DggxlHzbHZajUH4RxCEMiu84Of/p32B4A//+cdyxvtFizImbX61D5pgMP7b/1T2vLRf3+dYLm+cnz+q8XWnDNJR+iy9vR011WTKFY6jtum2v3y2MhoDUxk/tmJFeT2mlBrzxf7alJyKF2AdaK269hRexDICSdj8yxVQlxl5eyEODYZsrc5DmbVCXHMeEPyuHRcQK5vyfYnF+xub60t28vCzmG2G2Rs/vlyQ8lY2PY4BgCm4I1MpQPjllsKhj1Is3ScreXCfzVf0ooa2LHslu1+rodyn75257HGykfNNgDw83JZrVzPILZDoT/++118/PO/med/+vS3mL5cI6fHwV2OrBxxlu35ctPtYPeLf713OUMuOtdCtXRk36DUO9G35cR2fSnWvzhd6gIraz1G9ESekisHMe5vz0Hbi+d0skWzZpBS1IUV+noGda2zAO6YlNcvwcmHSJbIIYEtm1xjYx0rDD9jk+dB8DJ9SyJL9SmOfRujeP0p6KLWM8Nq8HyGhb4rI0faxtUdRqqfpjytxPdk1BjtkLb+VTmR9ThKO6/dBh+WynKYddrTQ0Cpge1YJi/7L1XswzvZl1vv/Oslm8PZ92gJWcywMFh3gE3lVNcBRyzHsXPkFHAuZrUJSMZcyH2Yi8jpmW2JZcL2meW6HiKF27EA4GOjc+i7XasRLmriQuwU9ruciK15M2qRNrDlJ/dYlh4Fhwl5ra9Mj4uuQkzTnNiV6DNuIPrSTx5L7XXfyVmQ66ORW45YxSreksPJgk/1UawzkZN+8m4+CnceHRnhYT065uA2sLC4/JppbXRUia1xeHcSYVy0Ws1xpnAwLOdl8ouz22yrO7zkKPNT9/QmEdVksTq5ecw4LhIjxHiZ3O2EAmsitVLvlc1G/6o4e+SHMq16ImcjZKWDEfHoU7zaBhup7duMelQX+5zhxDnooiGKGh2IsqHDQdwGwdxVDjjW1BzzLck+O2ZrCTriyOTKVoKNsLIzB7VBRKTBxvRH1QbF4YdiKa+lDyOUTmiVPwSW9m/bdAJ6HN4V382qFfhoOdZObZQpDrHPovvPztWM1v3kgx/YdVFiwFFTE9WT84wjCmL+2tBjA1lICqzP7vSwRaBVodpI0y1AhUMtRAQrbWgZh9iGuGQb4l2yWumUyviasC1OPUqHo8g9tEFxHI5j/cWPUxtJj8IjeU996qplD6zvvJT37EHEitq3X2nUt3+8Wl+jg9KxeRdUsWBKHX/cEbovO8jvUt/++JxWbURRyCv9G4xpw4oQbtT3MqllXh+uqQe1dGG8PakMxGbbpC6KteBz5JgTNrV8k6VzX2ZdRLaMRpf8LLAPviQnHJ9+8j55uLaf/eHDjPrCuQ/C9vHDSZ+hNkVLBPzziJtnC4YpINwOmJ+MmO8covz2jouNkypLubHjaNk/JZSNEejH6fjwh9eGVeiWiMefT7j97AvE+2dwr/4A409eQ3zjFue7mA9Sc9qxrI0Tx9f2Ur56/6eo3jnZ6tRhiRi/PiF+8RT/+PyveHv+PcbXn2A43VSdqnmt/l4sa9mqLHlzknNakRuwlT1OMsAFiKODe+UOv5l+B3f3KqIfgAgMCxtI7Nw78YLZ894HgN3iJ3ucvOj7IbmV3L5SJQMMQHg0Ivz4R3A/fIJw6xEejRjmiLio/htL04Rsk9ezlJM2TnxSqmk1zDqvaEdSZJebAeH1O7gYEZ1DHNZaxGbr5d/P0knsd1S28DTupdwvf/WXqB7WiZgFHNyaLg9zJtQxhoPI9S6JHMeIxSUMY4RDflE4wRlWB7AzDukc4nSF5WdC7dj8sGsqTrvk7BchVxAda22PoNWOYxtyY67Bcqh3S1EYAkeyqOqbaRJBANQxKLVDfjVNVrRBHa8SrHZO8V0xrSWAuSwDkYaAyZj1AJsYOziC3TjN5Rzhm/VyjtFH/1uAc9aOwdq7N++m/M1TDZiGqboURlOwI10Vt74qRniFbSKqWfOYkq8f+O2e9UeDQUp5bVDlE1krOp3Vw6v8WBx27bvlBJ7fXTuW72mqLuEw7nTHLXnZADds6bgar7hoyJznMSdptRnuynytRFSLPHP2Usf2RMPZ0S32KttUZgDwcZqIWNQeGZok5uWR6sOkYyuWHps02pbXnIQo1owDgOwvVha5P7rayQrf6GS3VLAHvB7p/4/JQp6scVm9SMDOkdBeBxoZR7H0nh24FCR0hdwcaO/hbhxMLsbBOBKZjy++sQakRhwZK7ByD0SXUhExXanWziFtK9MqfEOcIxQ5aizHqiNSY1hPKgksfyfUTqR7PoL3bOd7TplC6eV8pqg3II6j2C3NstXrkkp8kAPFprZlI9jxSXrlvAkHW0UTe7//h2aV9n9VD8LCLGQJ/QAAAABJRU5ErkJggg==" id="imageb009c9a4db" transform="scale(1 -1) translate(0 -51.12)" x="118.8" y="-149.04" width="51.12" height="51.12"/> - + +iVBORw0KGgoAAAANSUhEUgAAAEcAAABHCAYAAABVsFofAAAIzklEQVR4nO1cu44kNRQ9dnl2d1jtig1ASPwBAYI/4CGxCSk/gAiI2T8ACYlgJdL9BiJCJPgGkBCkBEhEsGKZfXa1bYKqrrq2z3XZ0z1EOJkZ17nH18f3Xruqq8f89vtrEXOzKNtA+gBgMKboY/YTR4kFAEs4VCxhZz5oWKvwDkbzWp/P/w2A83PcDAYI2UULwBOjAYCPMe0zprBfOTLsvIoh47DG6NiM3cIWPqx+lNiAEmth4GOKlZHknsY5ceLBoCSZjIgjEiscpemZRLWCFXQsndM0WpeNcVw6lYUP7iJcIwQkBmImRkJOViUXM1awrQui+MBrQ8wWZMbyoehiuOfxbDKSgszRNGThOS5EsegvBFXEGEnf4ngmxkjEOPjQIuhYWZCJo+SVgrrdklardsM80THT8zDIKPoWUaS9EFViq/ZAllYltkVUJkgy3oaoo7jsLsK5Ai5Ty9KSy7H6ap2Ag0RpHuU1jtay4S7CjdWIDMAK8TQoEY8NqjhYiBE7hKvUPz6HtpQH0jk4H9fM87AFUVh2sbTfYyiEO3DlwgWUwvmlrq0cXlQB6UeYeaUP8oghsR4lbxBzZByJvSgP7mm4jrzpEcD7i52pgu1L13Ze5nNTxAJq1LoLf6MEV4h6UkfD92H/uwXJF8O9iI4PjogxZruVCcmulGA1hzK8NaHEHsSiWL5j5voy7IQPxTyYsAAwZKdlNwbt1pKdO4ZiFb5481vV/vOfPyw741Bsp+sZIy+cQxFN2lnLwuDeG9+rvgDA17++P/PyKA3Zqdo99mXNWQbs2KlYe7Q/7wp/ilXSWDuv1JosISqv3K2e7K9nFzvqgYI9tGc+vTVhHD0LoPvWJtRjf71rAVweSiGuf9tkO1zT7zBA2PBpjDYR8MCR8q54KV6Y64ecjBdYKZ70udY8LOVgcwMA98yfqWQsdFtXCQB2QS/2tJ9wq7ujyWvOtl9yri3p7h6Pa1pR5zq23bxdzNx80toJtw3bMn7eWkpIUnNGv4aU6chpOz8UuvfjR7j/9jfF9a9+uYvdX1Pk9AjcXleGrigGgBckkksfhB7v/PDZ+gy5I3KYkL0F89hI7eK9RKS65/tVTVbWehzoi7x2ZzXuqxNnWgi32/Oi2etQF1YZL8efYqxjssH5YHSA3NbnX73o07GxjlWcT/Aot+gFS/p95PNgHPnxRePdLMitq6zZa/1WIW6Nip4U7plDcs4Zx/TeSvmcjAunitFmr/JyF5pF0oVr9wsAXPCVz/USQzOTKVDqZLszLfa+im2vb621zOU5mX68VNaM4nO0pV9iQbHGxASXYsuawew5ltsDxZMNGFafDr/kNSeMWeR0RIaOZZ3tK8vHOkUKt2MBwEUpDvV0+lFQVBzgWOYZs+/BUheobyq0Jk4SSixlmK1BEYJQUmni4KnExsvTAwqWpd2KJWkH0liai98dfO12X1zrSKu03xT2cRPLONPxY22DKLBsrKkjjxz5lzNjWwgDRhUjNglXmbjWyG6Z2+sTz+05rmbvzF4J46zR50nz4CY3qtSulv5V7CN4qX1H9ANwRTKSxUp4JZlQLBEv4Yikrz6ekWQKNm7xgvSzFVaOJgDgbHNabUWPZq8IuDEWjyh5+RT1kPWvAGf3dScSe34oKe218TvEYT6o2y7z4dLirM2xT0n080PZxSeh2JNG3lzTORTiPiEVP5g4lmzluur8cHVMuqj2GodSBahnR6axM/lns6DpVx8Qx9cTyqGsfle0tkY7wTrLXhetkGiNOdeb9zlHVGZ8VbUrx7rkrTPtWNAy8dq9ooKlHNomRLZ0/fiwwaEcQXLNnBG7lb4dE6INbMtK9ozH2qYPGkejD86SmlMb8BRbZA/HscVa5WgYyxlZcxpIaofNVuf0gn+F4zWInI/lTO1tAKqu4Zcqziel4ZJR05PGm+NtiHq47IZdL1FUBzg6XVSntb33iLEasMXtw1WEb+u5Qh8rXlka58LLv4q7cn73rQwaxc8ImBgRjUG0k/0SpqfckhVsNMBPD+6h1t769H4xnpH3L8Vute+rOSU54J5FnD3xsLuA4AzGWw7780mknpQ4SXRU2uHY0lqg3bDTlWNOrNi5MPuIGw93OPvjEfDPY5ibL2F4/Q6evXoNwRlMIcUpjhGjhtXaQI4tNV5XfQfIkMPt0jGJan3E8HQPPPwb3/35AHfvfILhzk3Y/Rki+7xQRFLPrnLZHSiB+HIzSXlTkiSt+GT4n8uW7oHgLIbzc3xw+2OYm7cQBwsTgMN9WzoZvttVzzMAFbXn3g8AikAofIgJr7PiOzR9d9azXQDiNQv/ysuwt28iXHMI5w7Gx/WLX1qqNCzGpXxT2jCW9bUmsLMjzytqpLwa4c8swp0bMPH6slsZH8uVynirJ+WiXy5ix7lHtCUQGs9T5t33vozaxToRG52DW1JlG9vYh855VKLXGRE5hpEqL2FwB7TnJIpnBK+KQ4Tvedbd91x8+uHsLnvapa4+OUmeJCKOXBDFj1ZsteYYnxWGwNEsqnpWWX+xh0SbV/Adnz4Y5WvAPYvsjE9J9AmzF4a0RCZ92sQ66lTXc+KeTzWUSHVml955ahOmYaq9FMZWsocXAP0XE/prZYRX8Y3WLg522IkzNX+7Z/rR4FDlLchm3uZF2BCqdvLNeZOrAps97JKn5RmU3LUKGtbvFazMsw1eQ3kFk/Zyz0FAYm/K9+cK3pVbfFaOMXugY9fbAkq41af0L2IfyUuvWNPGS/plVOUWLu52YI3WCDU8SdxrD5FOlI4t/bGBV0spQNacjChWHcnF8OoETy3yNoeYuN/AbvjmcPhGbBGWFmVeH+pFBjYsrKeJRsZB9lljDOAJL2vWlhys7i392jwIVvS5+PyF7kDeOlZcPQPRA6LyVY2uwyTxtyeaCbcLLzJxlP9hZZRJ0WZN+cJUTypNBs1Yfk+oi1gshIJ1RWjJlBGDJpm0iOJFlxiAYCPbzoEklfgkLcUmvjG8SK+UV3CwHVQs+P//0KzS/gUy17kR35P5XAAAAABJRU5ErkJggg==" id="image8e5c15d305" transform="scale(1 -1) translate(0 -51.12)" x="180" y="-149.04" width="51.12" height="51.12"/> - + +iVBORw0KGgoAAAANSUhEUgAAAEcAAABHCAYAAABVsFofAAAItElEQVR4nO1cS64ctxU9t4p6n0g2YgQJAmQHGRheQmxPvRCP7R0kgGcGMs0yMk/2ECBAphkkU8uRJdlPXV3FDKqrm7w8h0W+lmAPzIlU7MNzLy/vr6qrn/37P7+NOI0B5RjJHACMZsUcW79ylFgAGAiHxBJ2poPCDoJ3NKW13s/PA0CYY3LFjctHjOWcOEmgxI4wLJRDCVwIrThbI1jlB7HEbt4Uvo9J4ERgIBtZF7ANurnTZml4FpsW2NgTyjOXFfUp+1CmYXzaVni53AgSZ9FIjHEWUM4P3pixgm09kCoHAYIcSqzlxnyEh/jkIiA1yMmjxsSdp4woFvP5+jrWz190SLA7HC1GnSoGXTkuY+PdDBoOWViNGJ3HTNiMFN08867SoBuWKcc5NFZu0HnYRDhqBvUeuhk0vFzuqcB1UZmsBpYYBRZQIdDOoQ1Seqk/lKoODV4eXoucsyqrcgwxWodxlCEHWjkEL6l0XQcnPDTdR5hikGSLW7sJn13qGi1idmV1M6BXa7AFc3TrT0r62jPagsXxXnQodS70OvGmHKkBU45tz6lu4fvltlCeeTL3lhMw7mPPCgiDl9jYzMt0lpVVpQoCDy/m+wTUEQJuXinTg9WhWTmYBqzcA8UmOWdyLu4NtH1eCIhjhs1KtKhi2+ZlOU90SQ1FK2byX45N5jJe1sym2Mt8mBbej5blsSzzAPCnD/9K1wPAH//5WT7hDAqkvQUxftFWnLBFZ75iv/z936QuAPDnf326NoGyXcnnw3fHNayYS/e4Lhsvjve7J7UnT4eEDmM1Xs53u9xZtfphfuI+bM8HqtRv44f50iaofNJzAFq3NkO9mm+7DiBMSZkbLWJxN23DuRy63GSxKPV+bNwrL89tRbuwtREo5c0Oe24XKjea6ZgxUA62NwAIh6Xsc1Ll2aLWwbh7eHu8tSXMtihp9dbwavJ9TttdswqTdLw8cfeEg9okxTbokI7Xx9tirnbgYZovLmVUAaXs/kPEzXNajVs1YhF+UT/sEuON82TaPCY62B/+/sXlGXLHSfYZsgPb2CDWTtx/VsVCY8PD8WJNltZ6NswM1svxUziM7SDC4cgTcq9CXVim5DuSdc0BhHnhZdAsAr6sGzCTssmxUWO9sgLLSvRAZAFAFJ4sOcjwRgvHRSe1njBTIcWNIeT9yJ7q58PhUIaV+oaFKcTnrlsPXH8wOv+16xaWmXiOVKISgsVcuxLa6/LruYolvATXY+DgY9IAGtNmsfwe70QYPYep7/yiwLbJY+sVh1mkaZfNqT2HZRoIkhAwi0ssm2wPHy6rzbvk+k4sAISYGkfdvxmxeEUBjmWasfU9WKoC1U1Ca8ZBWsrViVNf5Btg4dTjYfIGu8MblW5s1G5Xc+O0KqXme7AQhpCZnCaxZqwyOk8B6z/BpopxLEG6/+aC1QmShT1vcmS8JUcmtSPEUmyNI9ixrrh28+TudVvY6TlcXt0gTbwMtHuA5VzITFeUasFpKKwWjazflCK8Wp41YWONt0HfC5HWNww+rLryQ+o9iuNywTm4vD0vtsyr2tuMfQ+8XIThWFciW1LM63CSRavDQNSJlCtfW/nIfGDfkly7CaDSg1CC9m6hxxDSmI37CMNMcgfVSZdH5VA9ClGOxlxF20ZWKYUeSocwHCoAdXF188ahe3lNYbXObfLU+rA9mvXrG78KOi9uXl9RnnPwk2/yVqXHyeB7OofiK2rVFtS8peU+UZZksr6mA1vveVUhO2N5BfXygrlqpULVE+1h+ypQG5aNqg6KozEkw+hyjiLpEbg33xNy15bja+SdS/neCTBrP1ZoTwK9VlYGaTjU9DKYehuACjUO6RD6WA99zOFdIw9IwqqNKIr5t6NMHUs+eJfhCXf78DY2xqYf8yyl5Hh8pytloW707K68p3SaW5eGZxwMcWhoC5L5x5XkfP0//vIlauOjz7/OZQGwSKLhfFd+rDwodEaNxDMsAuEhIryeMR4WLE8GTE9HHO8NS/lTmXcWji33clvbop8955dhPPjvP9oVghmGY8Tt8wk3//0WePEK9uwXGH/3AR5+c4vzG3UVxQve3p6nwSjbGKdyrtZLFR2yEmqUKMLmiPHhCPzvO8zfPMc4vY/xV+/B5hsYe1GVVLyWjT+mpBeQ7Z03WdlyknNY0feAZDm+fGDLmmPs7g7Ds2ewu7v1egaGmRu0r8zz9qHr3i/RlZKdL2PeBA5THlZtsZ+cwAIsNyPmX/8Sw/tPsdwELPcBtkRsd/zVsPKHImTRqrLD7cc4lfm1GlbDxOOKLhLfwi/BMH1wB4u3iLZWKpsj/allz70V67GuMdLZERrLun38yVexBqgJl1Z3RqyGAJF3/b1bxx4qrxUGc6Xcsh8V8EWb8ExWepG87BvNSp1E100fPglv1YnZ8j1UsYD53yZlOecNecIuFdoxSqIgX9+OZQfT59V94cd0CzYniWEj9DejZ6OUp1yMoWyrqVHPE1yWf/C/NqACW+hQYveMxT4Nlr3vHgVJLE7SlHJLJOU3NucnpQNP3KUsAPzdIIGt/U2GYIc8rNTbW9RoilhujiVfgaV9lzr9xqQO0JShvCrgkPTU/O2e9Z89hdTLdh280vUfYage3jyHXq7cwy4Xp4OVProt9vPVu0GHZbwgXrtd+4rCxG0GJPqa+rsbjnc14gUbMCVh5U7fZnS5spqP3qtmjW3lXUMvmZs1tsab3Qq5z0I8HKAGzT/SRdlbqY0hCfCwrD2HUF7mRnEwDqtCCgACjqTP2YipcUQWHsp7hbdl3KZ5kfOMvbnWyBswk4AGTkZguYLgSfzCBtCXXdUmev4eDzNkz7NY/aZ2dhniw5t2BTqUVS0BDx/xU42eUJMeTVqFVs9Z3jjjCCEmNqUU4q+UsFNUfz2pHct5tREL3QQ2lCU5iY9EaBZNZ6PMyZSqHJcNncMswyYcewaZhW4Mn3T+OW/Csekh9P35D5pVxv8Bzi2dx3/WZpcAAAAASUVORK5CYII=" id="image612af7cbf7" transform="scale(1 -1) translate(0 -51.12)" x="241.2" y="-149.04" width="51.12" height="51.12"/> - + +iVBORw0KGgoAAAANSUhEUgAAAEcAAABHCAYAAABVsFofAAAMwklEQVR4nM2baZBdRRXHf933viXzkgkhiU4yyYQkEkFBtkILKVIqS0DEsrBQq1QU1AL9IKWyifgJQXa0rLJEAcvSUks+WJQLi2xFwubCKhC2BJgkBIdkksm8N2+53e2Hu7x77+v7liTzXk5VJ/f2Pd3n9L/P0ssbsWV8xBCQIwRpki014NDKByAt7TN5rT1n6WDnlZa+HZHBu+Q1a307Em/Gwclg6hY0vw8Lr6V9Ju8BBJrbMM2XRqKzWL0xlLWkZhwWOQ3Glm+Pvj22eSW3T6zl0fGVzOwukivVOXrZVr4y8iinzSlTWLoZgO1bllAN+ilJzZAQSXnRgHRT8QCQg0e3Zg5gNqi+bRUAbsU07UXSRKokNItknopp8FTtIB4tr6Gi8nxj4YZER+f+8zxe//wPE3WvAy9ccQtnXfPdqG6hnMN1Ow6novOsnbuRYwu7WOSUmFQVykZDIDthvYaBUM14ALhl4wLgpDXRHosCS5xQw/xr5woa2qGywE2wuf+daxUw+nA58Z5buonnHjuFqXqRscIOji3sir5VjLDrMCCqGN+mxQOb1hgAKbSV0aG1vmpy0fNip8xi6bHImUNh6WZq21YyqatMKEnZuBFvSdRT/bYCIYUdnGNWbOlmTPuNNo+PAOCWTR4AxyQVe6uxkMd3r2a0uItrjvpzVL9+8ypOW7Uxejep4FdYupkRYAQ4ee3VPPjIFQCc89gF/Omjv4z4frFxLc9NL+d9Q+9w/JxNVh0GRWXtu4xb1fmWj47QvFwZ4Zl3RnlneF7iW9xqOlEIDMB/3hxLfHt15r089vYhsASOLI636jBAF6sEoUaWdZ6qySVKWReY69YYnb+bJXOmEg3LutC1kNNXXRw9j71nZ+Lbotw0qxbsYHF+D2VdoKwLSR1MnrLJ9zQZ+4uqxqVqXNyqybdkBQfNafOe56oP3cWOLUs55q9XMO/n8ylM1vh046GEK4k2M2w2CeAm/+VkOOms6ylM1nn1fJf7h3/C94+4m63jI6yvjgYN4joMznJCA3ArgVullTlppR8HFi7bxoJPzOWhBy8F4LjaM3stdMNfLgke4FAuB2B0+XZ4dZSKLiAtwX8QFFqrW9OtZpvOXIXN70bPFUuM2mdldI5qSo+szNUPqqQtJ6RwIfizlz7OYneKii5QerfMik0/xlQdFuWmE/wnnHMjj995MZ3oH5vez8onrkHVHI5cvYU73vtRFrtTTHjD7PSSayUp9MAWgBB3K9UEJz5bt952Fi/c9B0gma7//voHE3HG3CmAS6xCEvFoVbKfY79+E0/f9l1OOvN6jr7q6Qh0eQAsBCu6gBQaWdMuYZlROWZUjpp2I2DStLa4Z78o8PRt/tZi/d8uZXt1OHKtis5T0Xlqxo1Kv6lqXCo6jzuTsJzOAXHu6Fv7XZmJmbmMFncFOgzecsI47M4o/8EHJuvQYpaV8VxmdP6AcCmAMNS4dR0CEtudt5m9/21ZAsv2v0I1lXSfwWYrHxxZVTmqKkdduVGpejlOPvFH1oZ3Ta/eLwqcvroZxIcL1SjWtca//ltzXbvUtYtbV3bhE5fVGJu4jlypwT3HH87pq18C4O53j9grgWO33gAX+M/TW8eYeDbHiolrOejgaYpOg7oFBN96+g9OGGrctDmDr9RPj/wjH1vpHyGuGz6PM3gRgKMmr+x++xDfsV8Q4x2FN8eXRCeKZ2+4kKo6cBaBETih5aRPXePnOPdN3RE9ezrr9Lg3KsXOdKsqR025B0xADjGRDeXQUA71oHha4mnJH3acwMa3lvD7V47nqG/dHDXMOWqfhZ/0yev5yc7j2To+wnUvrGO6XqChHGrKpaZc6tpJlH5TGIdlw3OIl3pQHnhjDevuv4gr//0Zjvvqc1S3rYrcZF3pXAQGgQn+tZdTTrwqeltx+7VR/RnXP8ydrx7DiQ99m19vPIGachKlnir9pnCSIssJLSZ8r+4sMvRaHjle5GuLH4luEQDuK/+mKyEPPHpl9Dz/2eZi8/tH3M3MjjnM2VhkZmIomhBP+aXuuYnSbwoxkEpJlJJ4noPnOYTvuAavZFAFw/rKmqiht5czWVvYfH7mzWWQM9TnG3BMDJQQJJko/abQYl3lBcKj7BDcBBQVjTGNdDW/e+3DrH/kUCSG6vYch/3gFuZv0kytkPz2/I/w5TVPWoWsvvZmFj5vmB6VzBw6w9kbLkQKw/hLB5Ev1VFjHnlHRwCE93b7HtX2jRqBPq5KKRaSdBRDpQZKSabensfrTy7AqYM6rsL6b97AyLK3aWxbxafO/BLn8oRVyH2fO4xTL38ZgI/ccxkv3rsGpw7l5Yp5y6eYW6wxXS1QreUCHVqzVcZl6axSI/AO1yhfeqRWqIxouptQAgRoF4wSbGyUGAFeaVRRQ9lnvG/UFwMvU9u2kvpTjt+H48sIAfGURKdcxwZSP8nzInDsPq2UoBoCV9DMjIYfJBc9/wWWPDzFzheH2HnWECeLq3lgww+itus+cAVvfHYxP33549z1yNFMPFdi954h1Kh/k0hB4dUdKkqiAxnJhVZskTkAy4m8aexX19mnyaZU1oxaeTMk9+I6NnkZvFZry+TtTpaLSnG2nak0796A1dpHSy+ZvLZ+/dbpy8V2ANjltTZwRRqcdooAJgGIzSU692Fl6mHmrS6Yat8SQxO8Wf0m+3CFl+Q0HUxOxHvuyZ1aq6xA99pvFsNegZ1kcIWX4rMOogfryrQ4C2svFtcz6DaBPcijS7eKc3SyrE59JNvvnbX0y7pdobI/2uqiQdpquxBqBynO29kd2gLdo3W0A9qVXjumbEG2T730YXXfjAabLvtehiKzQ4fcciMQc6sWlfYC6Y5W0aZfG+Bt+5lFkkGSSgbkHtwKYsqLpEkLSPqdNWhmaJaIHYOh8PrO7eIeL9aq9TUxyG5GYwkNPQXpPlBoMFHMSX61N7KmY2vv2fxW9l4sqw8UuVUcnE4BNSumCJ1MMkb6PBm/i86WFRMyQGwIM3gilXcbUBNbEeMXEfyPALQPjNEpvli7zim9m2HMDkVulV4hW9N0t24W3+aobhaP7d1yUCRDy5FezB86+b4gsgBh/G9GCowDxml+FzqI+BqEMRghmnGlw8AHGWtCsrpV82vqsWUB4hchwDgGLf27TBNaggGhDEKFIBoQwcBjAGfKo81+rg/Umq26cR3bwAxIYxCeb0XCmJTlBO3SsaqDrHbXzLNNrW5ldSlhHYuRREBJD6Rn/A6NBiHQDmg3cDfhA5TeMnW7Sx+Emwkv3FtZ3CpUKDp6jA3OSNBCRIN2a4bcHkV+qoGseeicQ2M4R33YoTEk/T/MMr4fC92cCCOyonFKh30fa88km25lWtctkJzGwFUAjONbRhhfnLqmMFnDfXsSM13GKRaRIwvQuRJe0b+xkBqkMpFQI8HIptwwuLfokHjoH4VxWAoVuka6mCComqbrBHVSBXFFGWTdIKfrmMnd3PvurejJXcg9VZy6jvH5JezDL01ZmTqo5iz2k0L5rmxYAl+4/A8vQ+MuEav3BxbU51xOzX8R4RZBiAhUR5oIGBGAZRz//qqtUQx0+xDEnGhwaRJN84rHHOn5aVkGgAkDeiiHs3ABztwSJp9DzytiBMhG4I6maTVh59KYZPbLzJb9R6mZrRqpbbnstEoDRyUPx1Uph87PQ2jjLwpzEqTAqemWbQb4C0Oh6AqQQaT0yHISK2RozbmklQUw0crXSFB5iSk2/cRf5xhkXTeDbQboPR2894nCUOMKr8OBjhD+7MV32Bo/gwmBcYW/Qpb+ItDfPjSDuQj++s5IYXURe1aKxbeBZKvIcprg2P07iA3p9ZAhysHSgFAiGoifpQwoE1mO0DRBDkVajjTSOgxmneMvM1zRCEZtWw1n2HdymQ+kABbpv9UUqX1H2D4NeGilAybh+ZfLPjiZl3at9dGWImYBwhjQutlGCIwjovb2Y4lkbab7dEoQs0BhkmqNOW2iYQKYcNaDYwnCEoCDFs3nNv1FYhPaxZ4H8DOvEBMXr410m/9LYVc4XCRK/GBtsoExNovKAmQQ65wQHNGwrM9t7hSatyVOJJ6zFpUx3pbebYAP8jwniMMuITg2ZWL+LtrNZnRvldysZvGbdBxRdr7Mutmmplt18TMLAJlxlZC2nCzqFuhO/fSBQm9yTaOR+mJTNh6BY2TJJKKHzNcV4AOgEBxx2vB5iSCROThbSs26mLLyZoFm6SMjfVt168XSe5k4wDX1emfGXgRlWIN1YFlrmAzArKF+Fi3dNalsFZ0g9Ki4vf9mfTSwXlypF6Cz9Ohg3Sm3STZNphWfThHn2DvMnCVb9b65YTYA+2bFvQBrBacTWcHrIabsb6ts8u5fq/w/icuVLPeXAyMAAAAASUVORK5CYII=" id="imagea3508ef7f8" transform="scale(1 -1) translate(0 -51.12)" x="302.4" y="-149.04" width="51.12" height="51.12"/> - + +iVBORw0KGgoAAAANSUhEUgAAAEcAAABHCAYAAABVsFofAAAIoUlEQVR4nNVbTYwcxRX+qrp61rv+kQALEcQBCXzAQGRFsgQioEQhyAKUSwQXJMgREBfvAaEIX6LAgZ91boEjFw5JTohEEVIuOEEcESSShYz4tbHsgMBmZ3dmuqty6J/pqnqvumpmdjP7Ljtb/dV7X3316lVNb634/MsbDDqWwbdMCKIVkBQWDJbwwWJJzzQPDisJ35lgsD86R7aLTzvi0F2XVzCeg4+lxKp88IKJj78Ii5PRPhkRaKNnnMESg6BEZLELzDq1bTLngbGRpnHutAPIXGzrwzFjEkQ2vsgthxiRy5qbgzVpIgOA2jSqA6IHm8HAfSQJsTgfkugPzCC4sZ+FBXewflPtg3ECQA31SuVQaBKQwW+nxOJ8LIPgvA94glc+qp9q2+TVJ5coJQoroPF8JItNcUgV2+OQKHZj9Ue1WWdO25Eg1Arl+KSw7EB3UezWR4TYVjzHppnTWAfHBW6DWFhmlvaw2F7muAAq+JoY4frsKj6bHMYbX96NK3+5ER+8tt7i7n34JVx8Yhvrd/4Dt698hU29gsvloR0R+9e3fkDi+uyvn9xucyC4qaEeBAlZCtePH9z/KW646Wv87ZMc797/itfnzNvPAm8Dfz9zEU/e+y4A4M2Pj6M7ERnhlyNZ4f3Z58SOsYZLKLPVSOfewxYktJ/6MNjUxgrA2XY59b2pV2DtjJ5fLqsWK0rLjdiIXL9W5gDEIbDp2FH4xPtPI3/uAI5/+xEMc4ACAPwMuO23GxjePMFvxv/CWjYiCMVlK4ed1awsZsRWw3JQE+K2Pb/juUdPAQBMdrqXxNkXTwIAPvrnU7jz0IWpXzKdE4o6U1BjbagHvROjRlp5gApEnV5tQmdfOAlg3cNR9uH5G3HLgf+SYrMTQxRqTsBUc8sJtSmorXJAgyyS8xMqLq9idMSeiN4609M2jzXlJCS2Gmn3a97090WI0pgoBbbqAs35pYhyy2desbaZjcg653R3lFDgedNZ7y8xJpYw55fOnvl3qca2SndZ+fEscVJmDgB++quXgbfiyKxdO7QIzSaKneXzZPZWOeBPybVfNS7pV1RTkpnXfsf6afx74yS+fmy7V5xfDh7D8KFjyA79gO0iT1omIQ7zWlVOwmKrUWmnOhe4237i8few8czr+OOFn0MEltuRP/8Of/jJn3B2dAnvXDqKMUEopfguaqcC7AMqF6/NHO4oR3U8nF/FffvGeL1nBkff78OJtREkzuMdHEV3IlLqmtiB3Wu7zHvLiJoQy4oL3JA8880RnBtej0vDgzj25Ib1pbOxX9zze1y4rsDT5+/Cd+NVXBnti8pKKl4MNtXcckItYfHjt563olGEqLbKod/GY1P8ElgSmbYsU8VWheb+DsAIxWBTBk+TjOeQGi9WbBenisJfVinZ07yfLqOwyyk2W5DLopM5LWjqmXs5TwuYgl1+sZXmlhXXMeCM97E3xVa6cBgI74PV0URhqch7S2wAUKaQfC52Opk+Qha22+59sMj0Y/u5kS66bTOKrVBS+zETKiF7aJZN//hM84a1i2Lb4gTeeHoAdjZCbQsWu/YRJXaLjeQGQAkqcwKEDJGKHHbnxaZ90GK7WMqvU5CFW5ABmEBHMY8oSy+2/VCJwgd1IYZLuwTye1VsflkRQlXBKWx4lvaq2EqUYYDbZg20D0D4oIVysUsgNgAlixDJcHvcQOP6A7ssNjfh3XOOKL1EnDGdY7C037nFrtujxOZ8EBzIghzTsS+duR20a0stNgCV/NeOpEId72MZxVayCANinUXNEmVLLLYlTohQCvmYGtaX5ssgtrWV9xLiNpGdGvz/WWy7IHOEZlhqu5Vp/3k17paHa0ef3WBjNaZkyb99j50NIzhFfGyI0NxiJ5h3+HXiAVTmBAhxMyqE8chS56601GfAHLdEczciipu9Wy3R8vH+zJxSZyKsTYoAN3ZZdWcuuCQMIEsDUVbZYgRgMkBnAkb6+NTimyR2glHjdmN555yYmes6kQWQDzXUsIQca+hcolyVmOzPUA785ZFafGNq1SzW96oGAJQsjEXIJ+8zMgIwEhAaUCON/GqB/NshxHAEDHIU16zBSAEjJYysRXYnKrik+rEVfna1ZBHyW/1szzk8oc7L64a8AIyulpEcG2TDCeSVIczVHyBWVyFXcsiDOWRhoOt/QRH13WVO7DAHH1s9nv1SgbusqDKi5CSi8EnnkaicCWPQXsJUGUSeA6q+0qINZGFabae7FyG2GzAlq2a0ppywZQSAEoFzTrsWS7/NCEAYAxgDPcggD6wCgxxGSejV6h6OKABZ/z+Tde/aFdvx3bVmRmOXWqw14w6VESUnxNdy5q/wVkoLAMZAGEAPMhQHVwAzAISAzmVLgBI25u3jVJT+zJ7F+C/c03htQbYAZTf1uS3d7qcHstMHgDaQTZ0hxOa3dFqUpC+OEdaWk0AZUaJgXuhwM+fcOzBCdJZa9aEVpTFC7JQNwMNz90sSjCwnTmYr6YgzzRQmne3rChAwnV2sUYkKHCd2lwMrYKhORppVThixlZg0ezlHqH9JtL+SyydN7OpRzJKaL3tkYfrLyAPHToUvtnSMPXRRV3z4+x6EXxoaFtv1G88hVuxp5gRAZJp3BSFmPyY4u3wa32RWxfilKTRidx+zmQ1AoeBebDCEmtn0tmOCETtzc4q9QL/2Dmz7VWJCvNChZmMBoswtNsctlIFz+FXoikN1bFJxAaJ0bcfFdn3PkIEKBfUqMEBIEtU3QZS9JLYyk0lkR6JKMucDkUJoScUGAGXGkw6GD+47JAZVY/2rZSEfzqiWQfCahzLjcRucPPCkBCKw5KDq4D6YuRMtGW6kYFQVpuOx3JqaY+qC3A0uEomHglqDWhKhPW7csoL2VTaa6lDSAUtfTMORK3X8DJalJ1Dll8KCEFPTvktElwpBf0sE7hePMA7iZ2mRGWhjdycD/weWz4eecSIwigAAAABJRU5ErkJggg==" id="image2b64b1f4d5" transform="scale(1 -1) translate(0 -51.12)" x="363.6" y="-149.04" width="51.12" height="51.12"/> - + +iVBORw0KGgoAAAANSUhEUgAAAEcAAABHCAYAAABVsFofAAADF0lEQVR4nO2bMW4TQRSGd9bjSAiJAoHENSgiUSLBERBVDkBJ4TvQRFyAEqWCM9AiIQpOQBCUFFYUR4BCvBSWnHlvPZ/fGHf5/2p25p834z/v/bNrb9LpjwdDV6Av2pOujklK5rqv8FZxUnWsL+IQb7XG9Sp+/RpvdV3nTlJ95/SZbjwkDiB/vbxjOvq0XLcn3dLzizFTjWYecf08G8OOjblX11yIM9kap1jTzKuPCQ4SByBxAPnb5b3qYO88x9eyGQN/sj5GXmFj+PWJy75SrB/0xtU8oQqJA8inf+5XB/1xSSlZcpHXUg7/wbXzlgUvbg3KHIDEAUgcQP7+667poNvysl59jb85fLtuv/h8VI/R8NgxfpyIcV8/fGfGZl+eVWOQBylzABIHkJ5+eGnyKlGambKKP11zqcaO2ZY1o9bg1x9xcTc3HBIHIHEA+efFbdORjK/UJ3pvIj+I+hh/vR73vJa9kVcpcwASByBxAPl8cct0mHrFuvbXMS7zBnddpbbFqYexnje6PxOqkDiA/PdiWh/1qUv5WY41lOOu643KCrmwBoRQ5gAkDkDiAHK/gLdwXBEOUb+IetMW7gC+MnKObc8eax58LaIXCeKQOICcF/5WF9hlWo94aWOTYnTdhvKgvWAJlrx46ZBVKHMAEgcgcQB52uA5eJRDDL4FiK3dFqdOHMXQj3q7QeIA8vTc9QTTfJyeDdwd1hvF2dPeyhL085Q5AIkDkDiAfHAOT74tjwE7P3bUY+7Fxxq4Q8N/At14SBxAPljU309pSc+P72fr9qPnxxCz5e61Hoa4n05mZujw6Hgjb1tMZQ5A4gAkDiA9fvIq/CsXHol9/TbcxrSD+zva6Ve9gkfpIM+JQ+IA8vTst+3h9z7WzWiJeYzSf28lWAzCn7xlfWUOQOIAJA4gT+YXpmPg92s3t4nXddve2Y3F7Jxf4JEc3xvdAihzABIHkIf5me3po6XjdIXSSdE07/lvlaIl6PcSXV9fdsUhcQASB5Cv5vP6aIPnJDyuC26LN5EHoee4vUX9yX0mZQ5A4gAkDuAfk1SyRDGUC3kAAAAASUVORK5CYII=" id="imaged45c7f7c37" transform="scale(1 -1) translate(0 -51.12)" x="57.6" y="-242.64" width="51.12" height="51.12"/> - + +iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAABsklEQVR4nI2UMY7UQBBF36/uFUgzntEEJETADVaCG5CTbIiIOQCChCvABQgJCTfaEGlDAsQ1IAEJobFZN4Hb7mrbsyJozfevX69K457RU10kACSGT0NWNFlLAjOqrNngw5CTEcPhkB9OQEaAh1UQVX7UYU8yLcPSqp8kyOwqm3W8OWxmBcr0ESCWdQf2mdjt7+YAFSytQpga07QEJCt1a/eR+fl89YZ2F6bTNUW3TeC4C7Q74/ryNced0TbDOTYBPX7xLlXb3aLLdqeyIraNluGVxpP1GTx2zQrgP7erwcN1jt02LaZMDaPH7RDfF/9ulyaqhxTIzMflsxf77c0JSP2LRMld15KVzwIxbrocKACt6hlw9PI8U0JKxGb7BylhLmyuYQq7Rl83lzES8d7md9U4Dwy6r/xlPWHqAYj3Nz8XhQ9PPvLyy/PBo4RNiUDR788/8errBYEyUG+/PUtjYxgbvVZPcJt6DVSwQE98eOd7hvQEV5gDgnos+8GD6afhgUR8cPYjNxbTXCC4r6NAxiHegyCIj85+DQ/5LQZp+v8M+d2alLUwjJDvj2FYzgQNXf8AXD2vrUvE87wAAAAASUVORK5CYII=" id="imagec142451bba" transform="matrix(2.550857 0 0 2.550857 118.820571 242.941311)" style="image-rendering:crisp-edges;image-rendering:pixelated" width="20" height="20"/> - + +iVBORw0KGgoAAAANSUhEUgAAAEcAAABHCAYAAABVsFofAAAH9UlEQVR4nNWcv6sdRRTHvzN37nskWEaRWBh5KNGIqBC0shMhf4D/gWAtWIq1TWprEVvRIoWdYp1GE1QMMRjUwh9ESd69b3dnLHZn3+zM9+zO7H2/7oGQ3Nkz55z5zDlnfxJ199cnHSLR8QCABRkDgIVSyRib39ogumS+qCtY5jFwXU3sLhTXNU2CBmgALCIbtjce6TqXgGt6p0MjFo7OB1IYFi4B13SWE12Xwuh1kxhSXeta3RicWTndGRlSqrqfCxB6GEKqur9joFW38JxMrDo/g8UI83t/g8W02xcDPVxHOO5hSDnexmHWrguzM6IVh8Eg6XjMB0Js5EBubbgEsqjrgdIYuJE4v9r5RNcB5qFbdgpuYLw3pixikRbKbMyCHR2aBdsdHp+GPbTlYZtVB0d0PlhoCqrVHQG4xbDNQ7sbTSxYKIMiAtwu2ADLnEBXdEIC6kElu7S9sM2jKHPkIAMrA4AEVBjkFsMelBVfqFC3Y46zgjwa2G8/ezPRz5HPf35JBOVtm7XlDVkLCw13/7xe43HzLy4t/8bzyyV2L97F+rdncL/ex+3qAv6qH0OcmSWwgQ54BuxSWbnl6NoAwDyyO5HjvNS/YP7DO5e/TfR2L97FHoA9/AEAuHHnCm6vnsqC3ccgZsrmULyEmybBNo+andGOzUpFw+HquV+ygri2dws3v7sk2w1kNsAZ8tDujrcRAGZtzaiCNP7y0/ezAwmzU4LNhDdqeSNLJG4nbGPMfkZZbbpL+82SApcv6MZ39CjEb9gYbLNu0swZC2gOKHGXMvxJfWZTWCvhRBRmq9lvUqVxKNKTHVm8jxLY7BL/qEoqjEmKCwDMqsuc6T5TDsUL72scdgmUTbJn3+6M2G1jMwck8DHH3uCNO1dwbe9WViCroHQl2DSrjgGKF9ZOYrtJz5ECUtHEL/95BdcwDeejW29h9SdvyJK/kp40V7LKal0b8WAMJDT244Mn8PEPb+Ddy9+IAXz201V8/ftzqJq0JJntoqzatCE3fN2D65zKps/XRFjR70/uvY5Pv3qN6mrlgHvR/Izrp6kYjqopx+2E+TMH9SJQ4IbGd3kDXe7uWDIlFt9OxmCrF7/4QDzKApJ2n0MRnJ4g7JyrfcmXqZu0rPyz6WYwlh98SUacZdim7spKDjLPQY6NbYNtLMmcMSdQDnG4wlsQvtAi3dOFbWwTqarkH+KCUOB4HHaev5OEDQDGCZnTzmqNJSYpwBLdcX90ejzOFjoYG/eX00aMq9lMITQWaQ8lo9QKALY2XIGu7E80EfmKxaAOMkdM/bGxGCABMAN2a2Mi2wa64/5Uga4XM3xckhM8GZMUzhDsksz2YlRcVmKRZ44BcKTJltqgCicM+xBOCRQAboS82gQK++DhlGAbFZ/KxyYq+s8AlKAg2AjlLMI2Ojq5O6Y54ZiDinVPDvZUDFmwARjVyHqyE3ksPnQasJMYZsJOG3LgWHbCjbHxbYZtdM0m8rRTZCyW8d6Ro8vtnijsTgZltalj2cZ2wk56znTdZsgR7bJsgytMwZ5u1EPpy2oq+Pm7zKdsCnvKHxvPatTBzz5zEptF5NMpxw07219mDGz+oCFPBVSyc9sGmx0yKvq+fxgQO80LMRxXqU3A/v76e9zxhLzw/nV+ILzOiTMHOAxSxU9TNt59QXnT3Z8hybpZWak6VZiVzvHtSjDvxGFnyOSJCOTe6tBxGgwdcICygLKuP+60gtOdDTUTNnUexzb/RZ9vJ2Owja6HDrKhAD2YReWgawfVAG4BNEsFa9o/fl6J3SKAM4XdGcSwk+uckuB1A+jKwexbmP0aqnZwRqE+Z1Cf12gc+gzKs6uKY5grqoYI29s2LL2mAvKHdAMs1i0Y82ANVTVwywVgAbcwcErDGjVZKnN60qbZkzyqIWfhYVmJNc53tC0lB1VZqKoBqhoKgKotVN2W2phtOVtT5aOC4sXHNmY36TnDgDwUvkjVuL6xOaOh3ALO6LYJN20P0nCJZwp78CN4U1DQqEsk587AKAaHQGE13n+pv9Rozu9ANRZuoeGWGtCqBRSYp7BphG2vmgp+E0mSgl3n6IplxcTOAe1beNsu3mkF7Gg4qwHd/bauZWyRDTt0NHhaMFHuc6SHM2Lb6IZ/fzd55mjiSwDVfq3mF+evfRSQDTuJgQfOYJcKaychbAUHo6oITtEuCwd0GngJ7OlT+nwovanByYLDNrqy49cMOnlWHxAW6nb4bQh1zG8yhYwg3zpsUlJAe30mwe6z+81XPyRf9QgW+ecKVFUETstHUB4BmOVvJLNzYjCqCrbZT0oenZZAISURBkOzKscuGQxAh4ePIrMBwKgm0DwuKDl2hXGx/2jZbslFZSIBcIOKPNApgEID8g5OOANn2w1tB3aN8nAKdlMad0cA5UzA7uwY1MIDnZIFdcGnr3nKoYRy2rCNO6iGCsL3p6oIFjkdlGTmWYANwKA6AIT/XCcENfzYRwqIFO4WwzaONWQWDIQFiV86ExslsJXQEIg/GpdkF8iGfVhWGTs8WFDJbhJdcUEsjonMLstqoIc9sWbj/HVO/J0Ocxw6CW9Yp8CG11IdqNQu6Qc9VBupMt0pKCTe+J5bDddsYGMqnUJSn2k/UWKD8wACBJqAglSqmurGwQ91o5eTvV1SkuLaohvPJDCX3q224wFmv3jrgqFxXVgCCoAL/PULsuEuB+XAYrPR9vvMpHbjG1olxwvgf7ABnbMEyf+4AAAAAElFTkSuQmCC" id="imagee880ab7d38" transform="scale(1 -1) translate(0 -51.12)" x="180" y="-242.64" width="51.12" height="51.12"/> - + +iVBORw0KGgoAAAANSUhEUgAAAEcAAABHCAYAAABVsFofAAALx0lEQVR4nM1caawlRRX+TlXdd9/MgAgGIzNs8xxAohBUlkiC/pBlJBogBhMBDXEJQuIfkZjIsCghJjgT/WPikkgMS1ASDTEGIUSJBEgwJjhhUYSBIczIAMIMb95durvq+KO6qqu6q9+9772593KSynRXnTp16quzdfd9Q2+9tpERkCBCnSSafQKi0QcAMjG/jVck5Epq4T3mxWT/JEktsomV4CaTTExMgwAgOb/JC7QcBLfw7jl+LB2sHk2Axz2I8BBUfdAAOPG416M+CnY82LuA7saXk2N14ppCIW997J09mwAAuuQJAT1i02uta0yC9H+3AADUoOWkpk3DmgUDaHHGydOQc7v+fjOHeqvTpy7f7q/36f6aF9+68N1G334Tt0XDOFC2adMBk+GAySAWzTx6phu17c9eiK1HfRMExsm37cAT998ABoFBeC47sryyjaO7uIV3pz1wk797cNcOnHz/D/zYpY9di55RUVs0HSyaDpa44fkTpyXDWDIMkbPCgDtRu2/3J/HQ278EAPznpu9EE/893LiqBTfdGLvNwnV7/fWrd23BgJVvGYRvA06lg8nSgAVyENSS6a5oYi/hduOQWR/Po3XrqjEFLHE1Lnn6rhRSjxUkM9SimW8Mfm3zE1j6/a0Y9OZw1sJu4Nxq7Ni5t3HKtp/g2Ed7ePmSdcAN7YuccNePsHAn8Obp8/jE1f8EzqjGPv7H3XjhudvRnc/x1VMex6KpwBJoBudp0qKZhyAD+tnzn6kVgYxvfeRvEXOYgh9/eQHnbj70qfzeF84q128C8+WT/tG6xiTowZdOBQCoAXfikREWfYJae7ZKkdejXH+W1tNjG2rUQT0PMQqRgJ7Lj8AmvD6acYXUFvvkDEByuoiensNB3cVB3UXPzKFn5rD57ttb0/Wd+85bVSr/3ML10dhF667yd+dcuR0H9bxfP2ypmDgNcJZMF2JoFFzr6w76uoNXrvp+68THnz55VQv+edf26P7h/l3++ql7rsfAdNDT3agNTQdD06mLmji5g1H9Wmoe5WJHPS3x6oQUcjQLVwqpp7uQZKD62p7MuHGne2AyNUhfdyCoKXsWQA1ZAQyooZ5+eZ6ioYn1kAmgpkU9ba1YDWrgpE4vpAMLk3lWdhY8S1AcuYMSA91BZpRvA93B2VfuaJ94Wm8iCrn1XVLo6w6GWmEWlt3Xc+jrOYjcSK+Ea1dt+1NrSj7zhFdbx+ptudReHxsUHd8yLZFpiYFWqFv2dMDpYGgk1LCoFncu9e1T/9o6ccuGNyei0ECrwKWn/yQeUmYkAAmVm2YMyfcuoLNxV3Li2/mGiSiUmxiQUbFvkjRwGTwrJMJWaInLXrw4Yr7g7B/6653/W937nL/sqorHCw+7Gn9/pXph/vN/fRrDQvmWaxndT5ucW4tcW0BcywqJ55/cHEWEh5+6xd+98c7hq3p8+PH5X/B3Dx38Dbad/lk/ds8Nn0dhhG9OuUxLpCx70uRir9BaoKi1dW+2v3Qvhqs7ycbjw7u/9tfrH9mJugXnWiDXAlkx/fjjLacoJMKmtUD/g8v4+ypjQeiaALD1w9Vbsv2XnYFcS+Raeutx97mePjhuXaG1gAmaLiS6J72Lfa9tBIOwbeelOOO6HSAwth59DeY3ZKtK5e//6R5s/diN9in8iu340L1v+bEPXLMbWgtoLRoHpfX03SovJPJCgk68+/aGKZyz8AruO/dX/j58g/fRB27GM5fclhyr00reBG75bWVZ9Q+ZL37p5tY1JkGn/uEWEDGUKURDoeUKr2ICMWC4dzPMozLpsrP45OisVUAToAlcVO2ZPVW6vmnnJTjv4jv8/aC3uq8Plz52rb++4MxbccWT36jun/0ijCaYQvjGmsCaYPT04dGFgC4E6Phf3BEfVyrgpvRr03nc+a1y026a/M1AW3JI8NIK9HJrKdIEjiZS8nKUwPT88ZWv+mqDpQweizcl182vxb9leS0paIDGWmTMPiAAe5Vy2xjWCjaFCWEUL6CoGLGBVgBSCnE5ZQ2gJPoPHdhpq2xjUaKgZTfaNjmcwi3Ij6NfU0ZTh6mAneBTZGobTXGPWDgNVJ13+VM6lGCP0oHGlKuoiJnqvOkTbe9rzE9NmjDY7XtYWbJRlKojiK1EqmJ8XUnHgrJxWOVzpTtxtWj7Rtv7CMBL37u+ZeJkaPMO+5CsRBEP2A04bSsNfepjeHOgEhSWbPscOwOkrTAHEgtuTTwRaO+BX+E5g7FutYy1MSUGgo2RAJjJbj6YRAaIfjBhKF1bONErCdQTJtL2X5X4xUcSrFYlDWDj1upKglG+PwtDEoXdb4tboWEpYUwL4wsZi3QIMsuyiUqeMKgsjmZjEeMSaZvP0m4VBFAfG3zssEC4MSotx5kiYIExHQAyiFWmksUuho0AaFYAugyuhI4HQoWiIOz6TLC5MiuFGcnz6US2DQJ5fa14cvTP1Eno0q2ocBqnFWKqDXCNR5TuIwIeU7lbJacpvA7q2DXVhIm0XbZhORExAOIodjhXAltAjCKwsm7EohwvLPoir1K8BZDsqmU5QOWvRplsf4TDDGOSi8NVheyo7koEawkuCDurYBeYGSg3zaK8LQAq2AdqpjLlC67iGAe1Zvi0/B4I1D6Vi1pADonrrlZ3CwOIvLSQZTZFDEDbRRuVdu3ivWA9QUBuPqMwWReg0vQpSMMsS1cqs5YaMDp9A9k3IM1gSdDzAvl6Ad0lGFXFH7cWE/lUD9SKRczeekRhDzuqc0JLMcoyWOtgOAvTcwTdsSA5cOb3DdHZdwDIC0BJFEe/DzhmHfSchPt1vswYMgPIMIwCNKgqB7Q9BBf8Z+1ZVYUcWE5c6JVBUgMyZ4iMwYJgJEDGfmQhA8ihgdrfg3n9DZh+HzQ3B6kk5JFdkPtxQAmABZl9MHc5XWgus2DNimeEkjMYIQqrsG+6uiZtTcwWea7fboZ01Ye8AGcZHjG/A2cZKC8sENrOFYWVI3KO5EQyIx3KbFdUik4XHKu7Enlc5wA2JggCQHYzrkHYiTIjgBkyY8Aw0FEQ69fjwuwrQOcwcEeB2IIhJZWAW5fy8aeoihxR1OPebLOXSLmVIyK7EZ/G3R+EGQYVDNU3EDn5TenD5yGPOwZUaEAKmA1dGEmQGYOM8XHLV8iGIfJKLiVqLR+sD+WuxyS3r8pyHBHgdhE9SpQ1icgNoAly6CQR8vfNoTi8+tjHZPvlkCGHCfANAGY0aiy/fhqwaVFV5+Sm/Y8pDcrHAwJTkNpzAxiAJdnU3RUwHSrTv3UnkTPkUAcVsuX1ChiOHkStH8c0q5TuDEaRNkD9lMjGFACAsfmW3MY0W3dgm7UMRPVii0rXMWWFXFheJoAkgY0DmePHB0GADuPMbFO6KLh8n5NXFVjbSZGw362BckOmOnLBNlizIr8poU0MIuCf0/zrougja/y4vtwvN6ZBVJjyfU5uzYap9nUodDVdL+vLdxalEHCB6Ff4AoAQMdh1GYEb+ce51HvUWWQrF5AdONGLP6IYHBNUsH7jpXsUBqQ1UGjrikSAkuCOBKSs5JjKjQBE8cdRVYTOtkYWmQaIoJAHKSP09TBAupTr+oUFwbqYAQpt03gpgwEQkQ/oVkb8isLGKPfWq/YWbMZEhXUDRYW2AbFOZplHdW0absZK+msAloe5GewRAFeTgVqsSbrZFIh0CQ7yojUWVNyJPudCQoClqOKQG9MalLPn8wcQZsI2+TPOVjTUgAAUZ5k9yVbOckwEQciYChwpQUpayyk3TsaCA13xEVETeMNVXzIYz8hyylCjoPXyidO9AhSBH7hULlzNwjbm1CwnspDSyhpkgvHG2jOyHV0mqYuO/Hoam9R/8pNyOaBlYy1ld0JGq+Wm+lMAt+qQmj/+Wsos9VsnJZVeiXItvHW5/nSS8W58kNu/N4930HW9FOdZq3BuEUyt6I+nhE/1Dd5DD3ybDpY5llHP2o3UcT5dPlqxhGDbtTbQ7HIzBi3QNZFXDw01QPYrju86SbDXGstWAPTEwFkLTRXYZSz4/ywULf2254eBAAAAAElFTkSuQmCC" id="image88739b51ab" transform="scale(1 -1) translate(0 -51.12)" x="241.2" y="-242.64" width="51.12" height="51.12"/> - + +iVBORw0KGgoAAAANSUhEUgAAAEcAAABHCAYAAABVsFofAAAIBUlEQVR4nNVbS6skxRL+Ijv79IzOCOKFOyIuBAUVFXEt3I2Xu1ZQ0KUrQTcqLkVXghsF1/4H8S/owo0PxAtyD8L1CboaFU6fflRWuKiq7nxEZGWe6jOeE5vpkxUZ8eUXj8yuzqGff7rBiGRGFA8BAIwwNoOiK9hQdUXLMg5N1wi2Z6To3v2dOB4L/d8jR168LDJwRfecCNSDmOpK5HU2BN2ePDr+Mc2cvUHJWIWuYncqsRKpqu6ErLQnbAUnMl8GjPjRjGRdycbONYfPJML3MCPddGikDbhoPklmRcLtsp3LhqkVx5NFc4ZMgThRl3viY91S4pn1zEvWrOhymun2hI9koyxEXiUsHZ8JWabZ0MiVMdRkqoxBIl2ab0/aRTQxAz5ORYmUsYzzbFSTLWGoIVvEoLZc2GW7CB14uqoTAdCOqKQfXF6y7YrnhSA9pYBAGdDOySUmOygraaG7tPMmvfTgJ7vPpCy4m7Lvht/8cA++WN3rgSwnO5fZzz3wpeo/Jx9991iPQyfbrsd2qzhyGTJy8uB8gU+Xi962cCRQMnBGbVFG1MqK+3VnMtsu23C3krbUHcgJ8otbFgECcpkynZRBll7FaGTbpTvqAY2Q4j32S4mVY3ms99Dnb+OF+z9P7XpSk61a8y2Vk3aRbyMA7LpNT8gaUbltb0y+ffptLL9+RiS7Jls13VqJ24kUGHtaUFZTo7QHFAZCP9DlI3oIGdpJjmx76uaqggRoClGnuxLWtn/p5FquWyMrYSOKM9WuXVlZ7RekfdceF7mEtV1K+TpwIDl1cVkJXx9WHjkyKcPY2UmRAGlk15AyJXtO26OM3Q6b3QjRzDk+a/Qee/l9rJxVyRYDcw6kDFJSMUlZaYDIm+hv36Un5FdefB7fn9yVAqrYAA7ZlMMslu3adWNVBTogmCeu/YD//fHPrO2qrJrakIMslu3abSu8Q1Uc68e9cTk+vYFNK/etmsUfqinH7URsyJsmjlJqKI7y6189i8dv/xHHqxt49MP38c0HryZz/vWfd/HKO8/jyTuOcby6G5/9dh82LvSlkX0emRLL0E5yZNMjH7+pPq0pNblMFKcVi6/Sld0Vkx37so1Ly2p41+yCsXLwNSAvMtm28cpKXGgFIH2h3b+XjWzbCpmTc4LduLedVxFYo/v3km1bF6lS8kFdECpq/7KRDQC2bYzupDeWmBQJ9A+JY7oiRpFsVVVaaDA2nWyLPnPGFqQi3REYPswRUEJ2Z6NGV8emmoh8xWLRRD1HjEjOQ0ygkG2k/DGyIB6LflW2lusO/mz6umQMvDCmztf6TG6shuwDZ3ZEoKVGOhJLE4UxZZyFJlttV1K4xWSH5FSA5wzzNIWUC0S2pXgrz01UeGQ1SnlsvlxEsq2JNneWNEccy0TFuhPJrli8iqOGbACWmhKQBYAEooDLTbZcVjuGQ5GjkXdy0chWcUjnHNNIk+S0044rpY4vAtmJDW0SAZYctMcHBH85yQ7IibWLonRGx6KTYgy3hmylrA7ouNcvirImVb2j3MZYo9bLSjwfnMXxuG5dlOVp55HZQeaMAaqJXPLogpMtPbLUcCYaipfYIMmOic+f7P++95qMcUQefuM91dcgyQkZ2INMfs1Uos+GQKb7TAyAAWrTJldMtuLLxzZV4oqR/HUn5EihppbJAMwMbgEYAjH35ITnKqZysmVHErazMzWQk8tWMXP8SaO9w/kOWJm0Hzpk78j9Tj8m5MYz25omdFBCCtN+bybHsGvGbM2ghsEzgrtCcEcEN+/1WDSjDFQSeEaRjjAx2ck5pwi8YbABwIBdMxY3G8x/X8EsN+DFHNs7r2B95xxsun7k96Cs3T5ykwgsFP8Lt9ZG7JBeCYYsoP4DA7M1Y/7nBrNfb6K9+Tvo+jXM8Q80V2doFrNOiQHjuo85uzU9aWr2JK9qBLtJWZVElJnRzgjUdqVEmwa8XqM9PcXMWtCmgWkYxgEtuuZsXNof9BJOQRyKlEGGdefspuQEgAZSwqbB/W4E7s8yRxbm+jXMANDVq3CL7mKQ2XK3a7VhWYnlE/zh6yrgp5aVE8zE5xySyJFIgQfUYb9PM8NdscBd10F33IbWGrire3Iw9LSA34JsNcqjg51zxjFYs5W2kjRywePoF3i3MHCLxf5EbAhMgNm2YonEdiWQwdsCdUufcs7Rjx2DXWucfP8uu3O4sETYADyjbg4ziHvnDHS159tVMAlXHnQC5cyuEamdxG3E0jYiZ6SkUpzUEdNiV2rU9qdkHukz/oCr0D3A1begnShrtqYnR42SkYHuU5r7HkT7rBh8DOQQlZGtZYRwS2ZKSQF9PxxrI/9+4i05DNK1Hf26QjI0Rnaoq9ktx5AjO8UgzU91LW2j09AwMbwZVOxYLAkfjGA33E4rG7iQ2X5WB7IrYQlDmtmWtk0YzTFSlHG1Txikt4IOZRcYJ1u1K/j3eCAAFs6VgR9ZUJgpFWQfMANLbNcEscucnJMaxwMp50X2BLtnIduiUV7oVJCyq/sDkOLL30225c02VFDun1INICNsB5eNbAAW242nJCyqN5jezZOASl0SIuEi2ZpdiWwVgxYwKiPbG08zpwKcuED15nNog7O6OeLL/E3OdACWN5twJJM9sXPx9FhTUoKumlGFpA+6IjaVdKHvGoLlJvqNojBqDIDE/2JTHmGJiC6jbiHBGl5geHWVl6foWWFmeSRFEjVgNWVS04vO0BqKyPFFJEoDBFwqsmKbfwFaHGczmnLDQQAAAABJRU5ErkJggg==" id="image405a3abfdd" transform="scale(1 -1) translate(0 -51.12)" x="302.4" y="-242.64" width="51.12" height="51.12"/> - + +iVBORw0KGgoAAAANSUhEUgAAAEcAAABHCAYAAABVsFofAAAKWUlEQVR4nNVbTYwcRxX+XnXNzu7OeteO7fWuTGyvHf8l/AQcARLJAQwOcAgcQEJCOJcoEhIHMMQnJIRACQIlKEdAQiBAOSB+hMSFCIijEOBKHGIbkjjrv5V/196d/64qDv0z1d2verp3Z4b1u+x29av3vvrqvVeve2bo4oU5g5R4ROkhiMxIqIusLgAIxoZTl7HOYXDpCoddjxjd+f+yupzQ2xY5HAGeY6IbPGfj7iSQ3licNwGoTACFRjmDrOq6yS0TnRyxLt3AdjFybT3ZMjx8N1kG6Vse8bqcjdi1Sd7jCO/BTOlmhwpEsrLmE2c2Q7hs6ApvlDQ7zpJmHESUIc04dBkbPAZTIsoduimzsm7GeFApsJOijRr5uKEn8PLqIbzZ2I6d48t4+n2/Y+cDwPP/Pop/3l7A/tpVPDx1Ftu9Omrko2U81I1M+nNEahrHkT0XnP7WI28szmcwyLquZgGloma7t4I9soNJ8nChVcXPT38YU69O4tR+jafhJufH5z6Cyp9m8Or9B7Ht6Aoemj6HSZrARb+Ja6oW63FR6oGP3GEJV15kwyInBmkR6MGgJSoAOgCAuq5C3api+ryP9kxy99NSvzOOXe/4aO6o4Hp3U+ADhIbx0DJWOlv+BENKerOGIXZ5iXiQHMg0wHpnG6750/BIo2UqeOKRl3D5g5uxdWwVZ56cx6FdV1iHXz5yCpfevQXbKqt419hNvNLaAg8Gdb05occtPoGBz7iBCsdDIq24UBZk8Pfb+/DS6YOgusQTH/0rvvmeP8b3KQe5eSBZDXf/7Bmg7eHggUv40s5/ZMC4MQw/cuzaG9Ue2e53Whng9RtzmD1VwdTlDm49MrlmADv+XMH4LYWzn5tDfT5Z61w1xiM9ksip62q2IDd08rQSDJIxT2F5J6E7xZ9sRWV1p0Bzm0C1toLIL98C8Ck1zCLdYoJENtRYCMjVZ2j89PAvcOjRoK48/5WjiVQyjo4USKbc3u8/hzdPnggufgg8c/pTGX3uQADcx/wgJTqYbB5kW/Mnjq1kF9xPT72Or67B+VsnTwD4ugWmF4VctAKjOaUiiQuyBUU2C6SVLft3LQ0ETEON5XTQ/MEwTGmobL8nm6riJGSYgLiI5fyNqhls6LFMpMq2KgZy0NKMa53rlOIeWke7WbJlkcORkh577Z2dwO5BgIna9WTbPmpSIok2C7A65E6BgmzLr259aE3Oj9UeB+q965ZKHp3sxoyiwQmlqSoZDJm04gAd/u230PnPNGSDMHf9UuL4zu2QLb0XXzuI+379LEgBaqGJ+9WS058r1UZdA2Xbl07HFI61LmzCgReWQYtLuHJkx5qcf2LvWex7cBni+m2cP74HnbnsU3APg+cYH55E5SXR53Q18w41BUbXFG4fnkF1rgZgdU3O25cXcPvEDGRrE9pbNdp9al3e+DCEKy+y46d3KTtx8+wKrj02Dq0IW6XCmcXek/jDj/0Ar/zhKdbhX946gI/tPQcA+OLbx3D1My0QgNpkGx3lettb7GAYtEQZFAmRgfSV6xV4D1Ct2sHClhsY93ycuTGLL3zvG5jfdwV3HtyBv/3mKQAn2fkP/Og53HtgCcsfmMXVxRYe2r2ICa+LK41p3GwmH2Bdi6cRRU9byYwv6atsWkXvmaNX0h2LwK7yMLkK6MtLqO66J9ehrAP6/EVM3LsFRgVGm6qCrvbQDSPWtXhunIvqQQlXXqRvpRUHiAhodiq4vDoDAPB9D8sHAf/4+9HaSvj9T96Lz973L9bhyoKGd/wImjsIwmvg4spmAEC9PQafAePE0Hdp65eOlVYRBtr7wnfZrXOGs4PAojbK6bqiqth8p10eQrbP0SqlSpl/nIA4olzO+5Nt9U7/J7JValxqX7idWMYSZhkCqZQui3HDkS0RRk6/BTlRhoDSL73yCMhAckarKYxtGJEt4acKI2eQtea4yF1QWpezy79lXG9kBzbKkS2zr0v6gWfGnPNdoZ83lo7W4gsaNNmSfK70cxOZMce4YfK+tF1OYcRkJ8kpAd7kME/rIWUDkS0pfZQXnGjPMs5dysdmy0YkW4rU4W44zT6OeaLSuvmFvi/ZJRbvxFGGbACS/KRCsYW6x9K37may+bSKGU4Kvxv5TkZCdlkbBXsiKfzeRb/a4WpXijreCGRnbLgmESBJgb1dyrE1XqZQb3SyE+S4Jjkdu2RYu0zAuW+fKAGkuOz/zrMZKI60sqTELtsS2xoS2YMWrt9zphXfHxT1FExfL9mlCFynZDIIqcgBii8orRcVeztiXCUn11dBf4OWdL8HAJJ8k5PjDkSU3XlD4bhBfARkCEs5KVNQhx1E6X4PYDpkoAcy8Wkm13CEpBiPANGbJzQADZAOTxWK7LrJzrgYYUoBzsixrtZQPEkAOmJKhNGiAaEMSCNBYuaj4zJ1bchkcTywkQMkQebuqIoMm9wFRLfK1Bl+YDgifJOJbCn85G4aF8hICDCCgkgwACkD2TbwWgakDCAAf1xAVQmqQjBRNJngr6v+cBg438MSUtnIjk8rJynxlveutTTBJ2wGkG2D6k0fYzeboEYbZqwCf+sEWveMQXvRmR7sTKJAs6RQPoYhSpoHAJCk+Fbc9cRnBEAURo428NoGlTttiKUbULeWIaZqkJiFNylBkwQjCKSDghd/WTRFDnsAsKCGJ1GfY7vMpFU/QEaH0UBBwSXfgDo+TLuDF1u/xDHvcVDXh+hqCN8L9FSQfhE5vdPL4ZI51Ybe56R5ACBF111I7QJF4U974gWFJxEZwFQ8iKkaHsWToPFp6Grw0arXNSBDIB0SpKMGKLDtjlbrRfeI+hzhZ53EaZWQOPe5ewaesSq7MVATFWB2BrRlCtrzoCcrgCCIjulFjLEJpvweCkH6Om4NRQTDQxA5keTsXEJFUEJHjXvQVa+nH55moqsB5tMNgul/pDveFgxL7LSKNl4KxX//jj05ogvbkCAYSVBhlwyDsBbpsMaYxFxnmjDfLxnlg6foGmvNAWZJ3RQ5TEpxIKOehTyChoDQBsajuPchZYI00oh/hZpbZ1R250Z5pPN9TkiOc5cEs6DYIgHKwBNhmlh1KGj8QmcquNePcGetc/2meoDC8SDh6xBQWjv8a+d+6ohlSbN0WAJENip6p6KjrXA84gxSuH5PUjfluQApmbH0z56JeimU3nXG7oboc1LlxRAgqesni2E/UvLGY8MWMdyul7DrrD8DljhIrMiWUCq7gH6R0mc8Pu7XYZclZYi1h6JT235t/Mk9X0u+kcrMKrEg7uueA7DLfo10DdGXtZs/X8Jn4n6NwOPGbdBEr9NuMvqK25Wm0+UnxvNKABKOuC9lI0ynAad6THYJuxLdjjWYXRybKk6jDnJGSbrDLq0hNZOR40wd7lvuZeoDQ7pT1wWYIcixcSy2EmuzyOm4nTsAEDl+ZZXjqIjuegmPdLPfVs0jPN3nWQXZ+KlP9Qqki4EjTBndtMOkq+R4EE0lUsihOyiSKdveZuXj9HkOAa/scMbn/ABq1BCjtRA5aWHJcoG6i8n6H98r6I/7jDMqAAAAAElFTkSuQmCC" id="imageab2d6b52cb" transform="scale(1 -1) translate(0 -51.12)" x="363.6" y="-242.64" width="51.12" height="51.12"/> - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + diff --git a/lib/matplotlib/tests/baseline_images/test_image/interp_alpha.png b/lib/matplotlib/tests/baseline_images/test_image/interp_alpha.png index 5679a2f97df8..804cbc50d5f3 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_image/interp_alpha.png and b/lib/matplotlib/tests/baseline_images/test_image/interp_alpha.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_image/interp_nearest_vs_none.pdf b/lib/matplotlib/tests/baseline_images/test_image/interp_nearest_vs_none.pdf index 67e39f8cb112..d587fadb5013 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_image/interp_nearest_vs_none.pdf and b/lib/matplotlib/tests/baseline_images/test_image/interp_nearest_vs_none.pdf differ 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 6854b0ed81cf..4591ae25d848 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 @@ -6,11 +6,11 @@ - 2023-04-16T18:07:28.590874 + 2026-01-31T05:22:22.066367 image/svg+xml - Matplotlib v3.8.0.dev855+gc9636b5044, https://matplotlib.org/ + Matplotlib v3.11.0.dev1730+g53f57dd80, https://matplotlib.org/ @@ -37,7 +37,7 @@ L 72 114.545455 z " style="fill: #ffffff"/> - + @@ -215,9 +215,9 @@ L 315.490909 114.545455 z " style="fill: #ffffff"/> - + +iVBORw0KGgoAAAANSUhEUgAAAAkAAAAICAYAAAArzdW1AAAAKUlEQVR4nGOs/8/wnwEJNKS4M6ADJgwRLIB6ihhvLVVAcdOCQ+cG2k0AwjIIhcEmydcAAAAASUVORK5CYII=" id="imageea92a82f40" transform="scale(1 -1) translate(0 -192)" x="312" y="-120" width="216" height="192"/> + - + 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 b338fce6ee5a..3ff7ac577202 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..453ea0ab2342 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..e9bde5b99554 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-07-10T19:29:53.473547 + image/svg+xml + + + Matplotlib v3.11.0.dev1075+g945334b731, 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.png b/lib/matplotlib/tests/baseline_images/test_image/mask_image.png index 4779a5ea67e3..934e42483498 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_image/mask_image.png and b/lib/matplotlib/tests/baseline_images/test_image/mask_image.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_image/mask_image.svg b/lib/matplotlib/tests/baseline_images/test_image/mask_image.svg index 0f9181a6c1c3..95b1fb40fea3 100644 --- a/lib/matplotlib/tests/baseline_images/test_image/mask_image.svg +++ b/lib/matplotlib/tests/baseline_images/test_image/mask_image.svg @@ -1,12 +1,23 @@ - - + + + + + + 2026-01-30T01:57:16.776745 + image/svg+xml + + + Matplotlib v3.11.0.dev1729+g1f7cad29d, 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/mask_image_over_under.png b/lib/matplotlib/tests/baseline_images/test_image/mask_image_over_under.png index 5ff776ad3de5..7cdff0a5487c 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/nn_pixel_alignment.png b/lib/matplotlib/tests/baseline_images/test_image/nn_pixel_alignment.png new file mode 100644 index 000000000000..3821a8a517fa Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_image/nn_pixel_alignment.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_image/no_interpolation_origin.png b/lib/matplotlib/tests/baseline_images/test_image/no_interpolation_origin.png index c4b26de1a284..2ee3019d6294 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_image/no_interpolation_origin.png and b/lib/matplotlib/tests/baseline_images/test_image/no_interpolation_origin.png differ 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 index 0d6060411751..58d1ce37c42f 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_image/nonuniform_and_pcolor.png 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 index da2c0467864e..7197b828fd51 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_image/nonuniform_logscale.png and b/lib/matplotlib/tests/baseline_images/test_image/nonuniform_logscale.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_image/rasterize_10dpi.pdf b/lib/matplotlib/tests/baseline_images/test_image/rasterize_10dpi.pdf index e40335e22503..739de6ee17c7 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_image/rasterize_10dpi.pdf and b/lib/matplotlib/tests/baseline_images/test_image/rasterize_10dpi.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_image/rasterize_10dpi.svg b/lib/matplotlib/tests/baseline_images/test_image/rasterize_10dpi.svg index a4fda6a4e839..7e8f573a1037 100644 --- a/lib/matplotlib/tests/baseline_images/test_image/rasterize_10dpi.svg +++ b/lib/matplotlib/tests/baseline_images/test_image/rasterize_10dpi.svg @@ -1,12 +1,23 @@ - - + + + + + + 2026-01-31T05:22:32.107077 + image/svg+xml + + + Matplotlib v3.11.0.dev1730+g53f57dd80, https://matplotlib.org/ + + + + + - + @@ -15,7 +26,7 @@ L 216 72 L 216 0 L 0 0 z -" style="fill:#ffffff;"/> +" style="fill: #ffffff"/> @@ -24,11 +35,11 @@ L 76.235294 60.977647 L 76.235294 11.742353 L 27 11.742353 z -" style="fill:#ffffff;"/> +" style="fill: #ffffff"/> - - + + @@ -40,12 +51,12 @@ L 135.317647 64.08 L 135.317647 8.64 L 86.082353 8.64 z -" style="fill:#ffffff;"/> +" style="fill: #ffffff"/> - + @@ -54,23 +65,23 @@ L 194.4 64.08 L 194.4 8.64 L 145.164706 8.64 z -" style="fill:#ffffff;"/> +" style="fill: #ffffff"/> - +" clip-path="url(#p6d7104f26b)" style="fill: none; stroke: #1f77b4; stroke-width: 20; stroke-linecap: square"/> - - + + - - + + diff --git a/lib/matplotlib/tests/baseline_images/test_image/rgba_antialias.png b/lib/matplotlib/tests/baseline_images/test_image/rgba_antialias.png index 65476dc9a595..4a2566720bdd 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_image/rgba_antialias.png and b/lib/matplotlib/tests/baseline_images/test_image/rgba_antialias.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_image/rgba_clean_edges.png b/lib/matplotlib/tests/baseline_images/test_image/rgba_clean_edges.png new file mode 100644 index 000000000000..467a757f933f Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_image/rgba_clean_edges.png 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 31e62e4ed4cb..1aa443ee55b6 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/upsampling.png b/lib/matplotlib/tests/baseline_images/test_image/upsampling.png index 281dae56a30e..f5c68c973578 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_image/upsampling.png and b/lib/matplotlib/tests/baseline_images/test_image/upsampling.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 b0fb8a4af7f2..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.svg b/lib/matplotlib/tests/baseline_images/test_legend/fancy.svg deleted file mode 100644 index 9e56f5ed36bb..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_legend/fancy.svg +++ /dev/null @@ -1,776 +0,0 @@ - - - - - - - - 2020-08-07T20:03:21.839839 - image/svg+xml - - - Matplotlib v3.3.0rc1.post625.dev0+gf3ab37aad, https://matplotlib.org/ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 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_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_lines/striped_line.png b/lib/matplotlib/tests/baseline_images/test_lines/striped_line.png index 957129cb981f..758ef251d5ee 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_lines/striped_line.png and b/lib/matplotlib/tests/baseline_images/test_lines/striped_line.png differ 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 index f22b446fc84b..8553bf61700c 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_multivariate_colormaps/bivariate_cmap_shapes.png and b/lib/matplotlib/tests/baseline_images/test_multivariate_colormaps/bivariate_cmap_shapes.png differ 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 9b7644a861c5..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_patches/clip_to_bbox.svg +++ /dev/null @@ -1,515 +0,0 @@ - - - - - - - - 2024-07-07T03:42:10.383159 - image/svg+xml - - - Matplotlib v0.1.0.dev50519+g9c53d4f.d20240707, https://matplotlib.org/ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 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_patches/patch_edgegapcolor.png b/lib/matplotlib/tests/baseline_images/test_patches/patch_edgegapcolor.png new file mode 100644 index 000000000000..a3738e3f7973 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_patches/patch_edgegapcolor.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_patheffects/patheffect1.pdf b/lib/matplotlib/tests/baseline_images/test_patheffects/patheffect1.pdf index 45fd3b5b2b88..180b54f63bb4 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/patheffect1.png b/lib/matplotlib/tests/baseline_images/test_patheffects/patheffect1.png index 3cf4ce936c22..2e24a5215839 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_patheffects/patheffect1.png and b/lib/matplotlib/tests/baseline_images/test_patheffects/patheffect1.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_patheffects/patheffect1.svg b/lib/matplotlib/tests/baseline_images/test_patheffects/patheffect1.svg index 0970ea5da48f..efe1f300e79a 100644 --- a/lib/matplotlib/tests/baseline_images/test_patheffects/patheffect1.svg +++ b/lib/matplotlib/tests/baseline_images/test_patheffects/patheffect1.svg @@ -1,12 +1,23 @@ - - + + + + + + 2026-01-30T01:57:34.442076 + image/svg+xml + + + Matplotlib v3.11.0.dev1729+g1f7cad29d, https://matplotlib.org/ + + + + + - + @@ -15,7 +26,7 @@ L 576 432 L 576 0 L 0 0 z -" style="fill:#ffffff;"/> +" style="fill: #ffffff"/> @@ -24,232 +35,252 @@ 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"/> - + - + - +" clip-path="url(#p31ed4989a5)" style="fill: none; stroke: #000000; stroke-width: 0.5; stroke-linecap: square"/> + + - +" style="stroke: #000000; stroke-width: 0.5"/> - + - + - +" style="stroke: #000000; stroke-width: 0.5"/> - + - + - + - +" clip-path="url(#p31ed4989a5)" style="fill: none; stroke: #000000; stroke-width: 0.5; stroke-linecap: square"/> + + - + - + - + - + - + - +" clip-path="url(#p31ed4989a5)" style="fill: none; stroke: #000000; stroke-width: 0.5; stroke-linecap: square"/> + + - + - + - + - + - + - +" clip-path="url(#p31ed4989a5)" style="fill: none; stroke: #000000; stroke-width: 0.5; stroke-linecap: square"/> + + - + - + - + - + - + - +" clip-path="url(#p31ed4989a5)" style="fill: none; stroke: #000000; stroke-width: 0.5; stroke-linecap: square"/> + + - + - + - + - + - + - +" clip-path="url(#p31ed4989a5)" style="fill: none; stroke: #000000; stroke-width: 0.5; stroke-linecap: square"/> + + - +" style="stroke: #000000; stroke-width: 0.5"/> - + - + - +" style="stroke: #000000; stroke-width: 0.5"/> - + - + - + - +" clip-path="url(#p31ed4989a5)" style="fill: none; stroke: #000000; stroke-width: 0.5; stroke-linecap: square"/> + + - + - + - + - + - + - +" clip-path="url(#p31ed4989a5)" style="fill: none; stroke: #000000; stroke-width: 0.5; stroke-linecap: square"/> + + - + - + - + - + - + - +" clip-path="url(#p31ed4989a5)" style="fill: none; stroke: #000000; stroke-width: 0.5; stroke-linecap: square"/> + + - + - + - + - + - + - +" clip-path="url(#p31ed4989a5)" style="fill: none; stroke: #000000; stroke-width: 0.5; stroke-linecap: square"/> + + - + - + - + @@ -257,20 +288,21 @@ L 468 388.8 +" style="fill: none; stroke: #ffffff; stroke-width: 5; stroke-linecap: round"/> +" style="fill: none; stroke: #000000; stroke-width: 2; stroke-linecap: round"/> +" style="fill: none; stroke: #ffffff; stroke-width: 5; stroke-linecap: round"/> - - + + + - + - - - - - - + + + + + diff --git a/lib/matplotlib/tests/baseline_images/test_patheffects/patheffect2.png b/lib/matplotlib/tests/baseline_images/test_patheffects/patheffect2.png index af91778e7d80..0949dfe28968 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 08e4a9a6f08c..7632cd263a41 100644 --- a/lib/matplotlib/tests/baseline_images/test_patheffects/patheffect2.svg +++ b/lib/matplotlib/tests/baseline_images/test_patheffects/patheffect2.svg @@ -6,11 +6,11 @@ - 2023-05-08T08:28:59.785389 + 2026-01-30T01:57:35.109776 image/svg+xml - Matplotlib v3.8.0.dev1016+gecc2e28867.d20230508, https://matplotlib.org/ + Matplotlib v3.11.0.dev1729+g1f7cad29d, https://matplotlib.org/ @@ -37,48 +37,48 @@ L 103.104 41.472 z " style="fill: #ffffff"/> - + +iVBORw0KGgoAAAANSUhEUgAAAXIAAAFxCAYAAABusCOnAAAF0ElEQVR4nO3WQWpTURiG4aa9WtNrUnBgUehOnLlBhy7GFQidavcg0oEghKhxCQYc/LzwPCv4OHBe/s2HL+9PF/zT/fPv0xMS3i5P0xMS7q8O0xMS3iwvpyckXE4PAOD/CDlAnJADxAk5QJyQA8QJOUCckAPECTlAnJADxAk5QJyQA8QJOUCckAPECTlAnJADxAk5QJyQA8QJOUCckAPECTlAnJADxAk5QJyQA8QJOUCckAPECTlAnJADxAk5QJyQA8QJOUCckAPECTlAnJADxAk5QJyQA8QJOUCckAPECTlAnJADxAk5QJyQA8QJOUCckAPECTlAnJADxAk5QJyQA8QJOUCckAPECTlAnJADxAk5QJyQA8QJOUCckAPECTlAnJADxAk5QJyQA8QJOUCckAPECTlAnJADxAk5QJyQA8QJOUCckAPECTlAnJADxAk5QJyQA8QJOUCckAPELR8f301vSNhtD9MTEl5tf05PSLjb/piekPD62judw0UOECfkAHFCDhAn5ABxQg4QJ+QAcUIOECfkAHFCDhAn5ABxQg4QJ+QAcUIOECfkAHFCDhAn5ABxQg4QJ+QAcUIOECfkAHFCDhAn5ABxQg4QJ+QAcUIOECfkAHFCDhAn5ABxQg4QJ+QAcUIOECfkAHFCDhAn5ABxQg4QJ+QAcUIOECfkAHFCDhAn5ABxQg4QJ+QAcUIOECfkAHFCDhAn5ABxQg4QJ+QAcUIOECfkAHFCDhAn5ABxQg4QJ+QAcUIOECfkAHFCDhAn5ABxQg4QJ+QAcUIOECfkAHFCDhAn5ABxQg4QJ+QAcUIOECfkAHFCDhAn5ABxQg4QJ+QAcUIOELdcPNxOb0h4Wk/TExK+3Xinc3zd/pmekHC5HqcnJLjIAeKEHCBOyAHihBwgTsgB4oQcIE7IAeKEHCBOyAHihBwgTsgB4oQcIE7IAeKEHCBOyAHihBwgTsgB4oQcIE7IAeKEHCBOyAHihBwgTsgB4oQcIE7IAeKEHCBOyAHihBwgTsgB4oQcIE7IAeKEHCBOyAHihBwgTsgB4oQcIE7IAeKEHCBOyAHihBwgTsgB4oQcIE7IAeKEHCBOyAHihBwgTsgB4oQcIE7IAeKEHCBOyAHihBwgTsgB4oQcIE7IAeKEHCBOyAHihBwgTsgB4oQcIE7IAeKEHCBOyAHihBwgTsgB4oQcIE7IAeKEHCBOyAHihBwgTsgB4oQcIE7IAeKWu8+H6Q0Jx90yPSHheLOZnpBwXK+mJyT8Wv27c7jIAeKEHCBOyAHihBwgTsgB4oQcIE7IAeKEHCBOyAHihBwgTsgB4oQcIE7IAeKEHCBOyAHihBwgTsgB4oQcIE7IAeKEHCBOyAHihBwgTsgB4oQcIE7IAeKEHCBOyAHihBwgTsgB4oQcIE7IAeKEHCBOyAHihBwgTsgB4oQcIE7IAeKEHCBOyAHihBwgTsgB4oQcIE7IAeKEHCBOyAHihBwgTsgB4oQcIE7IAeKEHCBOyAHihBwgTsgB4oQcIE7IAeKEHCBOyAHihBwgTsgB4oQcIE7IAeKEHCBOyAHihBwgTsgB4oQcIE7IAeKEHCBOyAHihBwgTsgB4oQcIG559ulhekPCi/1+ekLC5tY7neO0X6cnJPzeXU9PSHCRA8QJOUCckAPECTlAnJADxAk5QJyQA8QJOUCckAPECTlAnJADxAk5QJyQA8QJOUCckAPECTlAnJADxAk5QJyQA8QJOUCckAPECTlAnJADxAk5QJyQA8QJOUCckAPECTlAnJADxAk5QJyQA8QJOUCckAPECTlAnJADxAk5QJyQA8QJOUCckAPECTlAnJADxAk5QJyQA8QJOUCckAPECTlAnJADxAk5QJyQA8QJOUCckAPECTlAnJADxAk5QJyQA8QJOUCckAPECTlAnJADxAk5QJyQA8QJOUCckAPECTlAnJADxAk5QJyQA8QJOUCckAPECTlAnJADxAk5QJyQA8T9BWBuIIhTG3alAAAAAElFTkSuQmCC" id="image46d953aa11" transform="scale(1 -1) translate(0 -265.68)" x="102.96" y="-41.76" width="266.4" height="265.68"/> - - + - + - + - + - + @@ -87,40 +87,40 @@ L 0 3.5 - - + - + - + - + - + @@ -129,25 +129,25 @@ L -3.5 0 +" clip-path="url(#pb18bcc5e01)" style="fill: none; stroke: #ffffff; stroke-width: 3"/> +" clip-path="url(#pb18bcc5e01)" style="fill: none; stroke: #000000; stroke-width: 1.5"/> +" clip-path="url(#pb18bcc5e01)" style="fill: none; stroke: #ffffff; stroke-width: 3"/> +" clip-path="url(#pb18bcc5e01)" style="fill: none; stroke: #000000; stroke-width: 1.5"/> +" clip-path="url(#pb18bcc5e01)" style="fill: none; stroke: #ffffff; stroke-width: 3"/> +" clip-path="url(#pb18bcc5e01)" style="fill: none; stroke: #000000; stroke-width: 1.5"/> +" clip-path="url(#pb18bcc5e01)" style="fill: none; stroke: #ffffff; stroke-width: 3"/> +" clip-path="url(#pb18bcc5e01)" style="fill: none; stroke: #000000; stroke-width: 1.5"/> +" clip-path="url(#pb18bcc5e01)" style="fill: none; stroke: #ffffff; stroke-width: 3"/> +" clip-path="url(#pb18bcc5e01)" style="fill: none; stroke: #000000; stroke-width: 1.5"/> +" clip-path="url(#pb18bcc5e01)" style="fill: none; stroke: #ffffff; stroke-width: 3"/> +" clip-path="url(#pb18bcc5e01)" style="fill: none; stroke: #000000; stroke-width: 1.5"/> +" clip-path="url(#pb18bcc5e01)" style="fill: none; stroke: #ffffff; stroke-width: 3"/> +" clip-path="url(#pb18bcc5e01)" style="fill: none; stroke: #000000; stroke-width: 1.5"/> +" clip-path="url(#pb18bcc5e01)" style="fill: none; stroke: #ffffff; stroke-width: 3"/> - - - - + + + + + - - - + - - - + - - - + - - - + - + - - - + + + - + - - - + + + - + - - - + + + - + - - - + + + - + - - - + + + - + - - - + + + - + - +" clip-path="url(#pb18bcc5e01)"/> - - - - - + + + + + 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/tickedstroke.png b/lib/matplotlib/tests/baseline_images/test_patheffects/tickedstroke.png index 5884d46dc1ce..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_png/pngsuite.png b/lib/matplotlib/tests/baseline_images/test_png/pngsuite.png index e8ba8c51be42..b845735b5355 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_png/pngsuite.png and b/lib/matplotlib/tests/baseline_images/test_png/pngsuite.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_polar/polar_alignment.png b/lib/matplotlib/tests/baseline_images/test_polar/polar_alignment.png index e979e7ebb6b8..7a12c5d5c783 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_polar/polar_alignment.png and b/lib/matplotlib/tests/baseline_images/test_polar/polar_alignment.png 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_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_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.pdf b/lib/matplotlib/tests/baseline_images/test_spines/black_axes.pdf deleted file mode 100644 index 96eacb9308d9..000000000000 Binary files a/lib/matplotlib/tests/baseline_images/test_spines/black_axes.pdf and /dev/null differ diff --git a/lib/matplotlib/tests/baseline_images/test_spines/black_axes.svg b/lib/matplotlib/tests/baseline_images/test_spines/black_axes.svg deleted file mode 100644 index 8f7ff932859d..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_spines/black_axes.svg +++ /dev/null @@ -1,64 +0,0 @@ - - - - - - - - 2022-10-24T17:29:00.034663 - image/svg+xml - - - Matplotlib v3.6.0.dev4027+g68c78c9fb1.d20221024, https://matplotlib.org/ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 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 4d0bb1aefc81..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_spines/spines_axes_positions.svg +++ /dev/null @@ -1,856 +0,0 @@ - - - - - - - - 2024-07-07T03:44:09.761597 - image/svg+xml - - - Matplotlib v0.1.0.dev50519+g9c53d4f.d20240707, https://matplotlib.org/ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 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 1b29bdcd1fc3..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.svg b/lib/matplotlib/tests/baseline_images/test_streamplot/streamplot_colormap.svg deleted file mode 100644 index a95118c95d97..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_streamplot/streamplot_colormap.svg +++ /dev/null @@ -1,2647 +0,0 @@ - - - - - - - - 2021-08-18T03:11:20.912289 - image/svg+xml - - - Matplotlib v3.4.2.post1692+gb0554f4824.d20210818, https://matplotlib.org/ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 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 1b14d45c3059..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 b928223c3206..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 2a2bec622027..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_streamplot/streamplot_linewidth.svg +++ /dev/null @@ -1,3098 +0,0 @@ - - - - - - - - 2021-08-18T03:24:40.872884 - image/svg+xml - - - Matplotlib v3.4.2.post1692+gb0554f4824.d20210818, https://matplotlib.org/ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 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 7d6beca0b3ac..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.svg b/lib/matplotlib/tests/baseline_images/test_streamplot/streamplot_masks_and_nans.svg deleted file mode 100644 index 17e4c78c2490..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_streamplot/streamplot_masks_and_nans.svg +++ /dev/null @@ -1,3845 +0,0 @@ - - - - - - - - 2021-08-18T03:08:55.311923 - image/svg+xml - - - Matplotlib v3.4.2.post1692+gb0554f4824, https://matplotlib.org/ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 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 0a91d0ddedd1..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_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 6f2498ca0912..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_subplots/subplots_offset_text.svg +++ /dev/null @@ -1,1804 +0,0 @@ - - - - - - - - 2024-07-07T03:44:47.680020 - image/svg+xml - - - Matplotlib v0.1.0.dev50519+g9c53d4f.d20240707, https://matplotlib.org/ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 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/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 2075eca4868f..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,16 +1,16 @@ - + - 2024-07-07T03:48:17.981141 + 2025-04-09T03:27:52.212752 image/svg+xml - Matplotlib v0.1.0.dev50519+g9c53d4f.d20240707, https://matplotlib.org/ + Matplotlib v3.11.0.dev647+g7c466f9a72.d20250409, https://matplotlib.org/ @@ -21,1098 +21,648 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - + - + - + - - - - - + - - - - - - +"/> - - - - - - + - + - - - - - + - - - - - +"/> - - - - - - + - + - - - - - + - - - - - +"/> - - - - - - - + - - - - - - - - - - - - +"/> - - - - - - - - - + - + - + - - - - - - - + + - - - - - - + - + - - - - - - - + + - - - - - - + - + - - - - - + - - - - - +"/> - - - - - + - - - - - - - - - +"/> - - - - - - - - - - - - - - + + - - - - + + - - + + - - + + - - + + - - + + - - + + + + - - - - - - + - + - - - - - - - + + - - - - - - + - + - - - - - - - + + - - - - - - + - + - - - - - - - + + - - - - - - - - - - - + + - - - - - - + - + - - - - - - - + + - - - - - - + - + - - - - - - - + + - - - - - - + - + - - - - - - - + + - - - - - - - - - - - + + - - - - - - - - - + + + + + + + + + + + + + + + + + - - + - - - - - - - - - - - - - - - - - - - - - + - + - - - - - - - + + - - - - - - + - + - - - - - - - + + - - - - - - + - + - - - - - - - + + - - - - - - - - - - - + + - - - - - - + - + - - - - - - - + + - - - - - - + - + - - - - - - - + + - - - - - - + - - - - - - - - - + - - - - - - - - - - - + + - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + - - + - - - - - - - - - - - - - - - - - - - - - + - + - - - - - - - + + - - - - - - + - + - - - - - - - + + - - - - - - + - + - - - - - - - + + - - - - - - - - - - - + + - - - - - - + - + - - - - - - - + + - - - - - - + - + - - - - - - - + + - + - + - - - - - - - - - - - - - - - - - - - - - - - - + + - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + - - + + - - + + - - + + - - + + 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 d7d66b644771..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,16 +1,16 @@ - + - 2024-07-07T03:48:18.249876 + 2025-04-09T03:27:51.909780 image/svg+xml - Matplotlib v0.1.0.dev50519+g9c53d4f.d20240707, https://matplotlib.org/ + Matplotlib v3.11.0.dev647+g7c466f9a72.d20250409, https://matplotlib.org/ @@ -21,902 +21,492 @@ - - - - - - - - - - - - - - - - - - + - + - + - - - - - - - - - - - - - + - - - - - - +"/> - - - - - - + - + - - - - - + - - - - - +"/> - - - - - - + - + - - - - - + - - - - - +"/> - - - - - - - - + - - - - - - - - - - - +"/> - - - - - - - - - + - + - + - - - - - - - + + - - - - - - + - + - - - - - - - + + - - - - - - + - + - - - - - + - - - - - +"/> - - - - - + - - - - - - - - - +"/> - - - - - - - - - - - - - - + + - - - - + + - - + + - - + + - - + + - - + + - - + + + + - - - - - - + - + - - - - - - - + + - - - - - - + - + - - - - - - - + + - - - - - - + - + - - - - - - - + + - - - - - - - - - - - + + - - - - - - + - + - - - - - - - + + - - - - - - + - + - - - - - - - + + - - - - - - + - + - - - - - - - + + - - - - - - - - - - - + + - - - - - - - - - + + - - - - + + - - + + - - + + - - + + - - + + - - + + + + - - - - - - + - + - - - - - - - + + - - - - - - + - + - - - - - - - + + - - - - - - + - + - - - - - - - + + - - - - - - - - - - - + + - - - - - - + - + - - - - - - - + + - - - - - - + - + - - - - - - - + + - - - - - - + - + - - - - - - - + + - - - - - - - - - - - + + - - - - - - - - - + + + + + + + + + + + + + + + + + - - + + - - + + - - + + 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 27f30e5a363b..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,16 +1,16 @@ - + - 2024-07-07T03:48:18.594236 + 2025-04-09T03:27:51.858300 image/svg+xml - Matplotlib v0.1.0.dev50519+g9c53d4f.d20240707, https://matplotlib.org/ + Matplotlib v3.11.0.dev647+g7c466f9a72.d20250409, https://matplotlib.org/ @@ -21,1098 +21,648 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - + - + - + - - - - - + - - - - - - +"/> - - - - - - + - + - - - - - + - - - - - +"/> - - - - - - + - + - - - - - + - - - - - +"/> - - - - - - - + - - - - - - - - - - - - +"/> - - - - - - - - - + - + - + - - - - - - - + + - - - - - - + - + - - - - - - - + + - - - - - - + - + - - - - - + - - - - - +"/> - - - - - + - - - - - - - - - +"/> - - - - - - - - - - - - - - + + - - - - + + - - + + - - + + - - + + - - + + - - + + + + - - - - - - + - + - - - - - - - + + - - - - - - + - + - - - - - - - + + - - - - - - + - + - - - - - - - + + - - - - - - - - - - - + + - - - - - - + - + - - - - - - - + + - - - - - - + - + - - - - - - - + + - - - - - - + - + - - - - - - - + + - - - - - - - - - - - + + - - - - - - - - - + + + + + + + + + + + + + + + + + - - + - - - - - - - - - - - - - - - - - - - - - + - + - - - - - - - + + - - - - - - + - + - - - - - - - + + - - - - - - + - + - - - - - - - + + - - - - - - - - - - - + + - - - - - - + - + - - - - - - - + + - - - - - - + - + - - - - - - - + + - - - - - - + - - - - - - - - - + - - - - - - - - - - - + + - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + - - + - - - - - - - - - - - - - - - - - - - - - + - + - - - - - - - + + - - - - - - + - + - - - - - - - + + - - - - - - + - + - - - - - - - + + - - - - - - - - - - - + + - - - - - - + - + - - - - - - - + + - - - - - - + - + - - - - - - - + + - + - + - - - - - - - - - - - - - - - - - - - - - - - - + + - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + - - + + - - + + - - + + - - + + 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 7132b252484f..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..15b0c247a793 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 0443685b49b0..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,16 +1,16 @@ - + - 2023-04-16T19:42:04.013561 + 2025-04-09T03:27:51.453043 image/svg+xml - Matplotlib v3.8.0.dev855+gc9636b5044.d20230417, https://matplotlib.org/ + Matplotlib v3.11.0.dev647+g7c466f9a72.d20250409, https://matplotlib.org/ @@ -21,398 +21,215 @@ - - - + - - - - - - - - - - - - +iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAz0lEQVR4nFWMu00EURAEq3vm3T4HBwxOCOOEyIEELgRCJwACwOQn7e4bjF2dDmPUVa3W6KzXwoFaogiUCRGQiTJ2DjKfTlRuUjaVpkJUeD9RafL3dEelKIsKGFdcFiOgDPn5eLhIXVJXvA+/HrQV/4b1r8NF/hwH5YLYBqggCgyKgVwoiozjN3YRMbCL9CBjEB4be9BiJZ/vP0gPDl4u2TSYYtl5ZfJCvty+0z0zeaZppWumaaF75rB710yeb97o2r50rXQVDegyTWZSoyn4A2BiWT69Db/oAAAAAElFTkSuQmCC" id="imagef5c2a23f35" transform="matrix(30.98 0 0 30.98 75.5 10.8)" style="image-rendering:crisp-edges;image-rendering:pixelated" width="10" height="10"/> - - - - - - - - - + - + - - - - - + - - - +"/> - - - - - - + - + - - - - - + - - - +"/> - - - - - - + - + - - - - - + - - - +"/> - - - - - - + - + - - - - - + - - - +"/> - - - - - - + - + - - - - - + - - - +"/> - - - - - - - - - + - + - + - - - - - + + - - - - - - + - + - - - - - + + - - - - - - + - + - - - - - + + - - - - - - + - + - - - - - + + - - - - - - + - + - - - - - + + + + + + + + + + + + + + - - + + 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_widgets/check_radio_grid_buttons.png b/lib/matplotlib/tests/baseline_images/test_widgets/check_radio_grid_buttons.png new file mode 100644 index 000000000000..c569e6afcf04 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_widgets/check_radio_grid_buttons.png differ diff --git a/lib/matplotlib/tests/conftest.py b/lib/matplotlib/tests/conftest.py index 54a1bc6cae94..3f38f50ccc7f 100644 --- a/lib/matplotlib/tests/conftest.py +++ b/lib/matplotlib/tests/conftest.py @@ -1,2 +1,3 @@ from matplotlib.testing.conftest import ( # noqa - mpl_test_settings, pytest_configure, pytest_unconfigure, pd, xr) + pytest_configure, pytest_unconfigure, + high_memory, mpl_test_settings, pd, text_placeholders, xr) diff --git a/lib/matplotlib/tests/Courier10PitchBT-Bold.pfb b/lib/matplotlib/tests/data/Courier10PitchBT-Bold.pfb similarity index 100% rename from lib/matplotlib/tests/Courier10PitchBT-Bold.pfb rename to lib/matplotlib/tests/data/Courier10PitchBT-Bold.pfb 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/test_inline_01.ipynb b/lib/matplotlib/tests/data/test_inline_01.ipynb similarity index 100% rename from lib/matplotlib/tests/test_inline_01.ipynb rename to lib/matplotlib/tests/data/test_inline_01.ipynb diff --git a/lib/matplotlib/tests/test_nbagg_01.ipynb b/lib/matplotlib/tests/data/test_nbagg_01.ipynb similarity index 100% rename from lib/matplotlib/tests/test_nbagg_01.ipynb rename to lib/matplotlib/tests/data/test_nbagg_01.ipynb diff --git a/lib/matplotlib/tests/data/tinypages/.gitignore b/lib/matplotlib/tests/data/tinypages/.gitignore new file mode 100644 index 000000000000..739e1d9ce65d --- /dev/null +++ b/lib/matplotlib/tests/data/tinypages/.gitignore @@ -0,0 +1,3 @@ +_build/ +doctrees/ +plot_directive/ 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/tinypages/_static/.gitignore b/lib/matplotlib/tests/data/tinypages/_static/.gitignore similarity index 100% rename from lib/matplotlib/tests/tinypages/_static/.gitignore rename to lib/matplotlib/tests/data/tinypages/_static/.gitignore 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/tinypages/conf.py b/lib/matplotlib/tests/data/tinypages/conf.py similarity index 100% rename from lib/matplotlib/tests/tinypages/conf.py rename to lib/matplotlib/tests/data/tinypages/conf.py diff --git a/lib/matplotlib/tests/tinypages/included_plot_21.rst b/lib/matplotlib/tests/data/tinypages/included_plot_21.rst similarity index 100% rename from lib/matplotlib/tests/tinypages/included_plot_21.rst rename to lib/matplotlib/tests/data/tinypages/included_plot_21.rst diff --git a/lib/matplotlib/tests/tinypages/index.rst b/lib/matplotlib/tests/data/tinypages/index.rst similarity index 100% rename from lib/matplotlib/tests/tinypages/index.rst rename to lib/matplotlib/tests/data/tinypages/index.rst diff --git a/lib/matplotlib/tests/tinypages/nestedpage/index.rst b/lib/matplotlib/tests/data/tinypages/nestedpage/index.rst similarity index 100% rename from lib/matplotlib/tests/tinypages/nestedpage/index.rst rename to lib/matplotlib/tests/data/tinypages/nestedpage/index.rst diff --git a/lib/matplotlib/tests/tinypages/nestedpage2/index.rst b/lib/matplotlib/tests/data/tinypages/nestedpage2/index.rst similarity index 100% rename from lib/matplotlib/tests/tinypages/nestedpage2/index.rst rename to lib/matplotlib/tests/data/tinypages/nestedpage2/index.rst 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/tinypages/range6.py b/lib/matplotlib/tests/data/tinypages/range6.py similarity index 100% rename from lib/matplotlib/tests/tinypages/range6.py rename to lib/matplotlib/tests/data/tinypages/range6.py diff --git a/lib/matplotlib/tests/tinypages/some_plots.rst b/lib/matplotlib/tests/data/tinypages/some_plots.rst similarity index 87% rename from lib/matplotlib/tests/tinypages/some_plots.rst rename to lib/matplotlib/tests/data/tinypages/some_plots.rst index dd1f79892b0e..17de8f1d742e 100644 --- a/lib/matplotlib/tests/tinypages/some_plots.rst +++ b/lib/matplotlib/tests/data/tinypages/some_plots.rst @@ -135,7 +135,12 @@ Plot 16 uses a specific function in a file with plot commands: Plot 17 gets a caption specified by the :caption: option: .. plot:: - :caption: Plot 17 uses the caption option. + :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)) @@ -174,3 +179,22 @@ Plot 21 is generated via an include directive: Plot 22 uses a different specific function in a file with plot commands: .. plot:: range6.py range10 + +Plots 23--25 use filename-prefix. + +.. plot:: + :filename-prefix: custom-basename-6 + + plt.plot(range(6)) + +.. plot:: range4.py + :filename-prefix: custom-basename-4 + +.. plot:: + :filename-prefix: custom-basename-4-6 + + plt.figure() + plt.plot(range(4)) + + plt.figure() + plt.plot(range(6)) diff --git a/lib/matplotlib/tests/meson.build b/lib/matplotlib/tests/meson.build index e4a00e850f7f..48b97a1d4b3d 100644 --- a/lib/matplotlib/tests/meson.build +++ b/lib/matplotlib/tests/meson.build @@ -99,10 +99,6 @@ py3.install_sources(python_sources, install_data( 'README', - 'Courier10PitchBT-Bold.pfb', - 'cmr10.pfb', - 'mpltest.ttf', - 'test_nbagg_01.ipynb', install_tag: 'tests', install_dir: py3.get_install_dir(subdir: 'matplotlib/tests/')) @@ -111,6 +107,6 @@ install_subdir( install_tag: 'tests', install_dir: py3.get_install_dir(subdir: 'matplotlib/tests')) install_subdir( - 'tinypages', + 'data', install_tag: 'tests', - install_dir: py3.get_install_dir(subdir: 'matplotlib/tests')) + install_dir: py3.get_install_dir(subdir: 'matplotlib/tests/')) diff --git a/lib/matplotlib/tests/test__style_helpers.py b/lib/matplotlib/tests/test__style_helpers.py new file mode 100644 index 000000000000..764bd5a0c88e --- /dev/null +++ b/lib/matplotlib/tests/test__style_helpers.py @@ -0,0 +1,83 @@ +import pytest + +import matplotlib.colors as mcolors +from matplotlib.lines import _get_dash_pattern +from matplotlib._style_helpers import style_generator + + +@pytest.mark.parametrize('key, value', [('facecolor', ["b", "g", "r"]), + ('edgecolor', ["b", "g", "r"]), + ('hatch', ["/", "\\", "."]), + ('linestyle', ["-", "--", ":"]), + ('linewidth', [1, 1.5, 2])]) +def test_style_generator_list(key, value): + """Test that style parameter lists are distributed to the generator.""" + kw = {'foo': 12, key: value} + new_kw, gen = style_generator(kw) + + assert new_kw == {'foo': 12} + + for v in value * 2: # Result should repeat + style_dict = next(gen) + assert len(style_dict) == 1 + if key.endswith('color'): + assert mcolors.same_color(v, style_dict[key]) + elif key == 'linestyle': + assert _get_dash_pattern(v) == style_dict[key] + else: + assert v == style_dict[key] + + +@pytest.mark.parametrize('key, value', [('facecolor', "b"), + ('edgecolor', "b"), + ('hatch', "/"), + ('linestyle', "-"), + ('linewidth', 1)]) +def test_style_generator_single(key, value): + """Test that single-value style parameters are distributed to the generator.""" + kw = {'foo': 12, key: value} + new_kw, gen = style_generator(kw) + + assert new_kw == {'foo': 12} + for _ in range(2): # Result should repeat + style_dict = next(gen) + if key.endswith('color'): + assert mcolors.same_color(value, style_dict[key]) + elif key == 'linestyle': + assert _get_dash_pattern(value) == style_dict[key] + else: + assert value == style_dict[key] + + +@pytest.mark.parametrize('key', ['facecolor', 'hatch', 'linestyle']) +def test_style_generator_raises_on_empty_style_parameter_list(key): + kw = {key: []} + with pytest.raises(TypeError, match=f'{key} must not be an empty sequence'): + style_generator(kw) + + +def test_style_generator_sequence_type_styles(): + """ + Test that sequence type style values are detected as single value + and passed to a all elements of the generator. + """ + kw = {'facecolor': ('r', 0.5), + 'edgecolor': [0.5, 0.5, 0.5], + 'linestyle': (0, (1, 1))} + + _, gen = style_generator(kw) + for _ in range(2): # Result should repeat + style_dict = next(gen) + mcolors.same_color(kw['facecolor'], style_dict['facecolor']) + mcolors.same_color(kw['edgecolor'], style_dict['edgecolor']) + kw['linestyle'] == style_dict['linestyle'] + + +def test_style_generator_none(): + kw = {'facecolor': 'none', + 'edgecolor': 'none'} + _, gen = style_generator(kw) + for _ in range(2): # Result should repeat + style_dict = next(gen) + assert style_dict['facecolor'] == 'none' + assert style_dict['edgecolor'] == 'none' diff --git a/lib/matplotlib/tests/test_afm.py b/lib/matplotlib/tests/test_afm.py index e5c6a83937cd..80cf8ac60feb 100644 --- a/lib/matplotlib/tests/test_afm.py +++ b/lib/matplotlib/tests/test_afm.py @@ -135,3 +135,11 @@ def test_malformed_header(afm_data, caplog): _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 59387793605a..6eebde1da92b 100644 --- a/lib/matplotlib/tests/test_agg.py +++ b/lib/matplotlib/tests/test_agg.py @@ -1,4 +1,5 @@ import io +import warnings import numpy as np from numpy.testing import assert_array_almost_equal @@ -17,6 +18,13 @@ from matplotlib.transforms import IdentityTransform +def require_pillow_feature(name): + with warnings.catch_warnings(): + warnings.simplefilter('ignore') + available = features.check(name.lower()) + return pytest.mark.skipif(not available, reason=f"{name} support not available") + + def test_repeated_save_with_alpha(): # We want an image which has a background color of bluish green, with an # alpha of 0.25. @@ -249,7 +257,7 @@ def test_pil_kwargs_tiff(): assert tags["ImageDescription"] == "test image" -@pytest.mark.skipif(not features.check("webp"), reason="WebP support not available") +@require_pillow_feature('WebP') def test_pil_kwargs_webp(): plt.plot([0, 1, 2], [0, 1, 0]) buf_small = io.BytesIO() @@ -263,7 +271,39 @@ def test_pil_kwargs_webp(): assert buf_large.getbuffer().nbytes > buf_small.getbuffer().nbytes -@pytest.mark.skipif(not features.check("webp"), reason="WebP support not available") +@require_pillow_feature('AVIF') +def test_pil_kwargs_avif(): + plt.plot([0, 1, 2], [0, 1, 0]) + buf_small = io.BytesIO() + pil_kwargs_low = {"quality": 1} + plt.savefig(buf_small, format="avif", 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="avif", 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) + + +@require_pillow_feature('WebP') def test_webp_alpha(): plt.plot([0, 1, 2], [0, 1, 0]) buf = io.BytesIO() @@ -272,6 +312,15 @@ def test_webp_alpha(): assert im.mode == "RGBA" +@require_pillow_feature('AVIF') +def test_avif_alpha(): + plt.plot([0, 1, 2], [0, 1, 0]) + buf = io.BytesIO() + plt.savefig(buf, format="avif", 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)])) @@ -279,7 +328,7 @@ def test_draw_path_collection_error_handling(): fig.canvas.draw() -def test_chunksize_fails(): +def test_chunksize_fails(high_memory): # 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 @@ -333,7 +382,7 @@ def test_chunksize_fails(): def test_non_tuple_rgbaface(): - # This passes rgbaFace as a ndarray to draw_path. + # This passes rgbaFace as an 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)]) diff --git a/lib/matplotlib/tests/test_agg_filter.py b/lib/matplotlib/tests/test_agg_filter.py index dc8cff6858ae..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 diff --git a/lib/matplotlib/tests/test_animation.py b/lib/matplotlib/tests/test_animation.py index d026dae59533..b34dc01e41cb 100644 --- a/lib/matplotlib/tests/test_animation.py +++ b/lib/matplotlib/tests/test_animation.py @@ -13,6 +13,7 @@ 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 @@ -158,8 +159,7 @@ def isAvailable(cls): 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") + 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 @@ -175,7 +175,7 @@ def gen_writers(): # matplotlib.testing.image_comparison @pytest.mark.parametrize('writer, frame_format, output', gen_writers()) @pytest.mark.parametrize('anim', [dict(klass=dict)], indirect=['anim']) -def test_save_animation_smoketest(tmpdir, writer, frame_format, output, 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) @@ -187,17 +187,14 @@ def test_save_animation_smoketest(tmpdir, writer, frame_format, output, anim): dpi = 100. codec = 'h264' - # Use temporary directory for the file-based writers, which produce a file - # per frame with known names. - with tmpdir.as_cwd(): - anim.save(output, fps=30, writer=writer, bitrate=500, dpi=dpi, - codec=codec) + 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(tmpdir, writer, frame_format, output): +def test_grabframe(tmp_path, writer, frame_format, output): WriterClass = animation.writers[writer] if frame_format is not None: @@ -214,18 +211,14 @@ def test_grabframe(tmpdir, writer, frame_format, output): codec = 'h264' test_writer = WriterClass() - # Use temporary directory for the file-based writers, which produce a file - # per frame with known names. - with tmpdir.as_cwd(): - with test_writer.saving(fig, 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()}) + 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', [ @@ -278,6 +271,8 @@ def test_no_length_frames(anim): anim.save('unused.null', writer=NullMovieWriter()) +@pytest.mark.skipif(sys.platform == 'emscripten', + reason='emscripten does not support subprocesses') def test_movie_writer_registry(): assert len(animation.writers._registered) > 0 mpl.rcParams['animation.ffmpeg_path'] = "not_available_ever_xxxx" @@ -295,32 +290,18 @@ def test_movie_writer_registry(): reason="animation writer not installed")), "to_jshtml"]) @pytest.mark.parametrize('anim', [dict(frames=1)], indirect=['anim']) -def test_embed_limit(method_name, caplog, tmpdir, anim): +def test_embed_limit(method_name, caplog, anim): caplog.set_level("WARNING") - with tmpdir.as_cwd(): - with mpl.rc_context({"animation.embed_limit": 1e-6}): # ~1 byte. - getattr(anim, method_name)() + with mpl.rc_context({"animation.embed_limit": 1e-6}): # ~1 byte. + getattr(anim, method_name)() assert len(caplog.records) == 1 record, = caplog.records assert (record.name == "matplotlib.animation" and record.levelname == "WARNING") -@pytest.mark.parametrize( - "method_name", - [pytest.param("to_html5_video", marks=pytest.mark.skipif( - not animation.writers.is_available(mpl.rcParams["animation.writer"]), - reason="animation writer not installed")), - "to_jshtml"]) -@pytest.mark.parametrize('anim', [dict(frames=1)], indirect=['anim']) -def test_cleanup_temporaries(method_name, tmpdir, anim): - with tmpdir.as_cwd(): - getattr(anim, method_name)() - assert list(Path(str(tmpdir)).iterdir()) == [] - - @pytest.mark.skipif(shutil.which("/bin/sh") is None, reason="requires a POSIX OS") -def test_failing_ffmpeg(tmpdir, monkeypatch, anim): +def test_failing_ffmpeg(tmp_path, monkeypatch, anim): """ Test that we correctly raise a CalledProcessError when ffmpeg fails. @@ -328,13 +309,12 @@ def test_failing_ffmpeg(tmpdir, monkeypatch, anim): succeeds when called with no arguments (so that it gets registered by `isAvailable`), but fails otherwise, and add it to the $PATH. """ - with tmpdir.as_cwd(): - monkeypatch.setenv("PATH", ".:" + os.environ["PATH"]) - exe_path = Path(str(tmpdir), "ffmpeg") - exe_path.write_bytes(b"#!/bin/sh\n[[ $@ -eq 0 ]]\n") - os.chmod(exe_path, 0o755) - with pytest.raises(subprocess.CalledProcessError): - anim.save("test.mpeg") + monkeypatch.setenv("PATH", str(tmp_path), prepend=":") + exe_path = tmp_path / "ffmpeg" + exe_path.write_bytes(b"#!/bin/sh\n[[ $@ -eq 0 ]]\n") + os.chmod(exe_path, 0o755) + with pytest.raises(subprocess.CalledProcessError): + anim.save("test.mpeg") @pytest.mark.parametrize("cache_frame_data", [False, True]) @@ -418,7 +398,7 @@ def animate(i): ) -def test_exhausted_animation(tmpdir): +def test_exhausted_animation(tmp_path): fig, ax = plt.subplots() def update(frame): @@ -429,14 +409,13 @@ def update(frame): cache_frame_data=False ) - with tmpdir.as_cwd(): - anim.save("test.gif", writer='pillow') + anim.save(tmp_path / "test.gif", writer='pillow') with pytest.warns(UserWarning, match="exhausted"): anim._start() -def test_no_frame_warning(tmpdir): +def test_no_frame_warning(): fig, ax = plt.subplots() def update(frame): @@ -451,8 +430,8 @@ def update(frame): anim._start() -@check_figures_equal(extensions=["png"]) -def test_animation_frame(tmpdir, fig_test, fig_ref): +@check_figures_equal() +def test_animation_frame(tmp_path, fig_test, fig_ref): # Test the expected image after iterating through a few frames # we save the animation to get the iteration because we are not # in an interactive framework. @@ -473,8 +452,7 @@ def animate(i): anim = animation.FuncAnimation( fig_test, animate, init_func=init, frames=5, blit=True, repeat=False) - with tmpdir.as_cwd(): - anim.save("test.gif") + anim.save(tmp_path / "test.gif") # Reference figure without animation ax = fig_ref.add_subplot() @@ -545,9 +523,28 @@ def test_disable_cache_warning(anim): def test_movie_writer_invalid_path(anim): if sys.platform == "win32": - match_str = r"\[WinError 3] .*'\\\\foo\\\\bar\\\\aardvark'" + match_str = r"\[WinError 3] .*\\\\foo\\\\bar\\\\aardvark'" + elif sys.platform == "emscripten": + match_str = r"\[Errno 44] .*'/foo" else: match_str = r"\[Errno 2] .*'/foo" with pytest.raises(FileNotFoundError, match=match_str): anim.save("/foo/bar/aardvark/thiscannotreallyexist.mp4", writer=animation.FFMpegFileWriter()) + + +def test_animation_with_transparency(): + """Test animation exhaustion with transparency using PillowWriter directly""" + fig, ax = plt.subplots() + rect = plt.Rectangle((0, 0), 1, 1, color='red', alpha=0.5) + ax.add_patch(rect) + ax.set_xlim(0, 1) + ax.set_ylim(0, 1) + + writer = PillowWriter(fps=30) + writer.setup(fig, 'unused.gif', dpi=100) + writer.grab_frame(transparent=True) + frame = writer._frames[-1] + # Check that the alpha channel is not 255, so frame has transparency + assert frame.getextrema()[3][0] < 255 + plt.close(fig) diff --git a/lib/matplotlib/tests/test_api.py b/lib/matplotlib/tests/test_api.py index f04604c14cce..4d0241264ddb 100644 --- a/lib/matplotlib/tests/test_api.py +++ b/lib/matplotlib/tests/test_api.py @@ -13,7 +13,7 @@ if typing.TYPE_CHECKING: - from typing_extensions import Self + from typing import Self T = TypeVar('T') @@ -150,3 +150,8 @@ def f() -> None: def test_empty_check_in_list() -> None: with pytest.raises(TypeError, match="No argument to check!"): _api.check_in_list(["a"]) + + +def test_check_in_list_numpy() -> None: + with pytest.raises(ValueError, match=r"array\(5\) is not a valid value"): + _api.check_in_list(['a', 'b'], value=np.array(5)) diff --git a/lib/matplotlib/tests/test_arrow_patches.py b/lib/matplotlib/tests/test_arrow_patches.py index 254b86cb54d6..e26c806c9ea4 100644 --- a/lib/matplotlib/tests/test_arrow_patches.py +++ b/lib/matplotlib/tests/test_arrow_patches.py @@ -11,8 +11,8 @@ def draw_arrow(ax, t, r): fc="b", ec='k')) -@image_comparison(['fancyarrow_test_image'], - tol=0.012 if platform.machine() == 'arm64' else 0) +@image_comparison(['fancyarrow_test_image.png'], + tol=0 if platform.machine() == 'x86_64' else 0.012) def test_fancyarrow(): # Added 0 to test division by zero error described in issue 3930 r = [0.4, 0.3, 0.2, 0.1, 0] @@ -59,8 +59,8 @@ def __prepare_fancyarrow_dpi_cor_test(): """ fig2 = plt.figure("fancyarrow_dpi_cor_test", figsize=(4, 3), dpi=50) ax = fig2.add_subplot() - ax.set_xlim([0, 1]) - ax.set_ylim([0, 1]) + ax.set_xlim(0, 1) + ax.set_ylim(0, 1) ax.add_patch(mpatches.FancyArrowPatch(posA=(0.3, 0.4), posB=(0.8, 0.6), lw=3, arrowstyle='->', mutation_scale=100)) @@ -149,7 +149,7 @@ def test_arrow_styles(): @image_comparison(['connection_styles.png'], style='mpl20', remove_text=True, - tol=0.013 if platform.machine() == 'arm64' else 0) + tol=0 if platform.machine() == 'x86_64' else 0.013) def test_connection_styles(): styles = mpatches.ConnectionStyle.get_styles() diff --git a/lib/matplotlib/tests/test_artist.py b/lib/matplotlib/tests/test_artist.py index e75572d776eb..6bb50826f7ef 100644 --- a/lib/matplotlib/tests/test_artist.py +++ b/lib/matplotlib/tests/test_artist.py @@ -120,15 +120,15 @@ def test_clipping(): patch.set_clip_path(clip_path, ax2.transData) ax2.add_patch(patch) - ax1.set_xlim([-3, 3]) - ax1.set_ylim([-3, 3]) + ax1.set_xlim(-3, 3) + ax1.set_ylim(-3, 3) -@check_figures_equal(extensions=['png']) +@check_figures_equal() def test_clipping_zoom(fig_test, fig_ref): # This test places the Axes and sets its limits such that the clip path is # outside the figure entirely. This should not break the clip path. - ax_test = fig_test.add_axes([0, 0, 1, 1]) + ax_test = fig_test.add_axes((0, 0, 1, 1)) l, = ax_test.plot([-3, 3], [-3, 3]) # Explicit Path instead of a Rectangle uses clip path processing, instead # of a clip box optimization. @@ -136,7 +136,7 @@ def test_clipping_zoom(fig_test, fig_ref): p = mpatches.PathPatch(p, transform=ax_test.transData) l.set_clip_path(p) - ax_ref = fig_ref.add_axes([0, 0, 1, 1]) + ax_ref = fig_ref.add_axes((0, 0, 1, 1)) ax_ref.plot([-3, 3], [-3, 3]) ax_ref.set(xlim=(0.5, 0.75), ylim=(0.5, 0.75)) @@ -226,8 +226,8 @@ def test_default_edges(): np.arange(10) + 1, np.arange(10), 'o') ax2.bar(np.arange(10), np.arange(10), align='edge') ax3.text(0, 0, "BOX", size=24, bbox=dict(boxstyle='sawtooth')) - ax3.set_xlim((-1, 1)) - ax3.set_ylim((-1, 1)) + ax3.set_xlim(-1, 1) + ax3.set_ylim(-1, 1) pp1 = mpatches.PathPatch( mpath.Path([(0, 0), (1, 0), (1, 1), (0, 0)], [mpath.Path.MOVETO, mpath.Path.CURVE3, @@ -259,6 +259,36 @@ def test_setp(): assert sio.getvalue() == ' zorder: float\n' +def test_artist_set(): + line = mlines.Line2D([], []) + line.set(linewidth=7) + assert line.get_linewidth() == 7 + + # Property aliases should work + line.set(lw=5) + assert line.get_linewidth() == 5 + + +def test_artist_set_invalid_property_raises(): + """ + Test that set() raises AttributeError for invalid property names. + """ + line = mlines.Line2D([0, 1], [0, 1]) + + with pytest.raises(AttributeError, match="unexpected keyword argument"): + line.set(not_a_property=1) + + +def test_artist_set_duplicate_aliases_raises(): + """ + Test that set() raises TypeError when both a property and its alias are provided. + """ + line = mlines.Line2D([0, 1], [0, 1]) + + with pytest.raises(TypeError, match="aliases of one another"): + line.set(lw=2, linewidth=3) + + def test_None_zorder(): fig, ax = plt.subplots() ln, = ax.plot(range(5), zorder=None) diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index e3a59a1751ab..58a2cf0b81c0 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -8,8 +8,10 @@ import io from itertools import product import platform +import re import sys from types import SimpleNamespace +import unittest.mock import dateutil.tz @@ -23,6 +25,7 @@ from matplotlib import rc_context, patheffects import matplotlib.colors as mcolors import matplotlib.dates as mdates +from matplotlib.container import BarContainer from matplotlib.figure import Figure from matplotlib.axes import Axes from matplotlib.lines import Line2D @@ -43,14 +46,13 @@ from matplotlib.testing.decorators import ( image_comparison, check_figures_equal, remove_ticks_and_titles) from matplotlib.testing._markers import needs_usetex - # Note: Some test cases are run twice: once normally and once with labeled data # These two must be defined in the same test function or need to have # different baseline images to prevent race conditions when pytest runs # the tests with multiple threads. -@check_figures_equal(extensions=["png"]) +@check_figures_equal() def test_invisible_axes(fig_test, fig_ref): ax = fig_test.subplots() ax.set_visible(False) @@ -156,7 +158,7 @@ def test_label_shift(): assert ax.yaxis.label.get_horizontalalignment() == "center" -@check_figures_equal(extensions=["png"]) +@check_figures_equal() def test_acorr(fig_test, fig_ref): np.random.seed(19680801) Nx = 512 @@ -175,7 +177,7 @@ def test_acorr(fig_test, fig_ref): ax_ref.axhline(y=0, xmin=0, xmax=1) -@check_figures_equal(extensions=["png"]) +@check_figures_equal() def test_acorr_integers(fig_test, fig_ref): np.random.seed(19680801) Nx = 51 @@ -196,7 +198,7 @@ def test_acorr_integers(fig_test, fig_ref): ax_ref.axhline(y=0, xmin=0, xmax=1) -@check_figures_equal(extensions=["png"]) +@check_figures_equal() def test_spy(fig_test, fig_ref): np.random.seed(19680801) a = np.ones(32 * 32) @@ -226,7 +228,7 @@ def test_spy_invalid_kwargs(): ax.spy(np.eye(3, 3), **unsupported_kw) -@check_figures_equal(extensions=["png"]) +@check_figures_equal() def test_matshow(fig_test, fig_ref): mpl.style.use("mpl20") a = np.random.rand(32, 32) @@ -237,13 +239,9 @@ def test_matshow(fig_test, fig_ref): ax_ref.xaxis.set_ticks_position('both') -@image_comparison(['formatter_ticker_001', - 'formatter_ticker_002', - 'formatter_ticker_003', - 'formatter_ticker_004', - 'formatter_ticker_005', - ], - tol=0.031 if platform.machine() == 'arm64' else 0) +# TODO: tighten tolerance after baseline image is regenerated for text overhaul +@image_comparison([f'formatter_ticker_{i:03d}.png' for i in range(1, 6)], + tol=0.02 if platform.machine() == 'x86_64' else 0.04) def test_formatter_ticker(): import matplotlib.testing.jpl_units as units units.register() @@ -334,7 +332,7 @@ def test_strmethodformatter_auto_formatter(): assert ax.yaxis.get_minor_formatter().fmt == targ_strformatter.fmt -@image_comparison(["twin_axis_locators_formatters"]) +@image_comparison(["twin_axis_locators_formatters.png"]) def test_twin_axis_locators_formatters(): vals = np.linspace(0, 1, num=5, endpoint=True) locs = np.sin(np.pi * vals / 2.0) @@ -400,7 +398,7 @@ def test_twin_units(twin): @pytest.mark.parametrize('twin', ('x', 'y')) -@check_figures_equal(extensions=['png'], tol=0.19) +@check_figures_equal(tol=0.19) def test_twin_logscale(fig_test, fig_ref, twin): twin_func = f'twin{twin}' # test twinx or twiny set_scale = f'set_{twin}scale' @@ -444,7 +442,7 @@ def test_twin_logscale(fig_test, fig_ref, twin): @image_comparison(['twin_autoscale.png'], - tol=0.009 if platform.machine() == 'arm64' else 0) + tol=0 if platform.machine() == 'x86_64' else 0.009) def test_twinx_axis_scales(): x = np.array([0, 0.5, 1]) y = 0.5 * x @@ -590,7 +588,7 @@ def test_cla_not_redefined_internally(): assert 'cla' not in klass.__dict__ -@check_figures_equal(extensions=["png"]) +@check_figures_equal() def test_minorticks_on_rcParams_both(fig_test, fig_ref): with matplotlib.rc_context({"xtick.minor.visible": True, "ytick.minor.visible": True}): @@ -601,7 +599,7 @@ def test_minorticks_on_rcParams_both(fig_test, fig_ref): ax_ref.minorticks_on() -@image_comparison(["autoscale_tiny_range"], remove_text=True) +@image_comparison(["autoscale_tiny_range.png"], remove_text=True) def test_autoscale_tiny_range(): # github pull #904 fig, axs = plt.subplots(2, 2) @@ -671,7 +669,7 @@ def test_use_sticky_edges(): assert_allclose(ax.get_ylim(), (-0.5, 1.5)) -@check_figures_equal(extensions=["png"]) +@check_figures_equal() def test_sticky_shared_axes(fig_test, fig_ref): # Check that sticky edges work whether they are set in an Axes that is a # "leader" in a share, or an Axes that is a "follower". @@ -812,7 +810,8 @@ def test_annotate_signature(): assert p1 == p2 -@image_comparison(['fill_units.png'], savefig_kwarg={'dpi': 60}) +# TODO: tighten tolerance after baseline image is regenerated for text overhaul +@image_comparison(['fill_units.png'], savefig_kwarg={'dpi': 60}, tol=0.2) def test_fill_units(): import matplotlib.testing.jpl_units as units units.register() @@ -857,7 +856,7 @@ def test_plot_format_kwarg_redundant(): plt.errorbar([0], [0], fmt='none', color='blue') -@check_figures_equal(extensions=["png"]) +@check_figures_equal() def test_errorbar_dashes(fig_test, fig_ref): x = [1, 2, 3, 4] y = np.sin(x) @@ -895,23 +894,7 @@ def test_single_point(): ax2.plot('b', 'b', 'o', data=data) -@image_comparison(['single_date.png'], style='mpl20') -def test_single_date(): - - # use former defaults to match existing baseline image - plt.rcParams['axes.formatter.limits'] = -7, 7 - dt = mdates.date2num(np.datetime64('0000-12-31')) - - time1 = [721964.0] - data1 = [-65.54] - - fig, ax = plt.subplots(2, 1) - with pytest.warns(mpl.MatplotlibDeprecationWarning): - ax[0].plot_date(time1 + dt, data1, 'o', color='r') - ax[1].plot(time1, data1, 'o', color='r') - - -@check_figures_equal(extensions=["png"]) +@check_figures_equal() def test_shaped_data(fig_test, fig_ref): row = np.arange(10).reshape((1, -1)) col = np.arange(0, 100, 10).reshape((-1, 1)) @@ -936,8 +919,7 @@ def test_structured_data(): axs[1].plot("ones", "twos", "r", data=pts) -@image_comparison(['aitoff_proj'], extensions=["png"], - remove_text=True, style='mpl20') +@image_comparison(['aitoff_proj.png'], remove_text=True, style='mpl20') def test_aitoff_proj(): """ Test aitoff projection ref.: @@ -953,7 +935,7 @@ def test_aitoff_proj(): ax.plot(X.flat, Y.flat, 'o', markersize=4) -@image_comparison(['axvspan_epoch']) +@image_comparison(['axvspan_epoch.png']) def test_axvspan_epoch(): import matplotlib.testing.jpl_units as units units.register() @@ -968,7 +950,7 @@ def test_axvspan_epoch(): ax.set_xlim(t0 - 5.0*dt, tf + 5.0*dt) -@image_comparison(['axhspan_epoch'], tol=0.02) +@image_comparison(['axhspan_epoch.png'], tol=0.02) def test_axhspan_epoch(): import matplotlib.testing.jpl_units as units units.register() @@ -1104,7 +1086,7 @@ def test_hexbin_log_clim(): assert h.get_clim() == (2, 100) -@check_figures_equal(extensions=['png']) +@check_figures_equal() def test_hexbin_mincnt_behavior_upon_C_parameter(fig_test, fig_ref): # see: gh:12926 datapoints = [ @@ -1183,7 +1165,7 @@ def test_nonfinite_limits(): @mpl.style.context('default') @pytest.mark.parametrize('plot_fun', ['scatter', 'plot', 'fill_between']) -@check_figures_equal(extensions=["png"]) +@check_figures_equal() def test_limits_empty_data(plot_fun, fig_test, fig_ref): # Check that plotting empty data doesn't change autoscaling of dates x = np.arange("2010-01-01", "2011-01-01", dtype="datetime64[D]") @@ -1218,9 +1200,8 @@ def test_imshow(): ax.imshow("r", data=data) -@image_comparison( - ['imshow_clip'], style='mpl20', - tol=1.24 if platform.machine() in ('aarch64', 'arm64', 'ppc64le', 's390x') else 0) +@image_comparison(['imshow_clip'], style='mpl20', + tol=0 if platform.machine() == 'x86_64' else 1.24) def test_imshow_clip(): # As originally reported by Gellule Xg # use former defaults to match existing baseline image @@ -1298,8 +1279,8 @@ def test_fill_betweenx_input(y, x1, x2): ax.fill_betweenx(y, x1, x2) -@image_comparison(['fill_between_interpolate'], remove_text=True, - tol=0.012 if platform.machine() == 'arm64' else 0) +@image_comparison(['fill_between_interpolate.png'], remove_text=True, + tol=0 if platform.machine() == 'x86_64' else 0.012) def test_fill_between_interpolate(): x = np.arange(0.0, 2, 0.02) y1 = np.sin(2*np.pi*x) @@ -1323,7 +1304,7 @@ def test_fill_between_interpolate(): interpolate=True) -@image_comparison(['fill_between_interpolate_decreasing'], +@image_comparison(['fill_between_interpolate_decreasing.png'], style='mpl20', remove_text=True) def test_fill_between_interpolate_decreasing(): p = np.array([724.3, 700, 655]) @@ -1344,7 +1325,7 @@ def test_fill_between_interpolate_decreasing(): ax.set_ylim(800, 600) -@image_comparison(['fill_between_interpolate_nan'], remove_text=True) +@image_comparison(['fill_between_interpolate_nan.png'], remove_text=True) def test_fill_between_interpolate_nan(): # Tests fix for issue #18986. x = np.arange(10) @@ -1403,7 +1384,8 @@ def test_pcolorargs_5205(): plt.pcolor(X, Y, list(Z[:-1, :-1])) -@image_comparison(['pcolormesh'], remove_text=True) +@image_comparison(['pcolormesh'], remove_text=True, + tol=0.11 if platform.machine() == 'aarch64' else 0) def test_pcolormesh(): # Remove this line when this test image is regenerated. plt.rcParams['pcolormesh.snap'] = False @@ -1427,7 +1409,7 @@ def test_pcolormesh(): ax3.pcolormesh(Qx, Qz, Zm, shading="gouraud") -@image_comparison(['pcolormesh_small'], extensions=["eps"]) +@image_comparison(['pcolormesh_small.eps']) def test_pcolormesh_small(): n = 3 x = np.linspace(-1.5, 1.5, n) @@ -1454,7 +1436,8 @@ def test_pcolormesh_small(): @image_comparison(['pcolormesh_alpha'], extensions=["png", "pdf"], - remove_text=True) + remove_text=True, + tol=0.4 if platform.machine() == "aarch64" else 0) def test_pcolormesh_alpha(): # Remove this line when this test image is regenerated. plt.rcParams['pcolormesh.snap'] = False @@ -1489,7 +1472,7 @@ def test_pcolormesh_alpha(): @pytest.mark.parametrize("dims,alpha", [(3, 1), (4, 0.5)]) -@check_figures_equal(extensions=["png"]) +@check_figures_equal() def test_pcolormesh_rgba(fig_test, fig_ref, dims, alpha): ax = fig_test.subplots() c = np.ones((5, 6, dims), dtype=float) / 2 @@ -1499,7 +1482,43 @@ def test_pcolormesh_rgba(fig_test, fig_ref, dims, alpha): ax.pcolormesh(c[..., 0], cmap="gray", vmin=0, vmax=1, alpha=alpha) -@image_comparison(['pcolormesh_datetime_axis.png'], style='mpl20') +@check_figures_equal() +def test_pcolormesh_nearest_noargs(fig_test, fig_ref): + x = np.arange(4) + y = np.arange(7) + X, Y = np.meshgrid(x, y) + C = X + Y + + ax = fig_test.subplots() + ax.pcolormesh(C, shading="nearest") + + ax = fig_ref.subplots() + ax.pcolormesh(x, y, C, shading="nearest") + + +@check_figures_equal() +def test_pcolormesh_log_scale(fig_test, fig_ref): + """ + Check that setting a log scale sets good default axis limits + when using pcolormesh. + """ + x = np.linspace(0, 1, 11) + y = np.linspace(1, 2, 5) + X, Y = np.meshgrid(x, y) + C = X + Y + + ax = fig_test.subplots() + ax.pcolormesh(X, Y, C) + ax.set_xscale('log') + + ax = fig_ref.subplots() + ax.pcolormesh(X, Y, C) + ax.set_xlim(1e-2, 1e1) + ax.set_xscale('log') + + +# TODO: tighten tolerance after baseline image is regenerated for text overhaul +@image_comparison(['pcolormesh_datetime_axis.png'], style='mpl20', tol=0.3) def test_pcolormesh_datetime_axis(): # Remove this line when this test image is regenerated. plt.rcParams['pcolormesh.snap'] = False @@ -1527,7 +1546,8 @@ def test_pcolormesh_datetime_axis(): label.set_rotation(30) -@image_comparison(['pcolor_datetime_axis.png'], style='mpl20') +# TODO: tighten tolerance after baseline image is regenerated for text overhaul +@image_comparison(['pcolor_datetime_axis.png'], style='mpl20', tol=0.3) def test_pcolor_datetime_axis(): fig = plt.figure() fig.subplots_adjust(hspace=0.4, top=0.98, bottom=.15) @@ -1552,6 +1572,30 @@ def test_pcolor_datetime_axis(): label.set_rotation(30) +@check_figures_equal() +def test_pcolor_log_scale(fig_test, fig_ref): + """ + Check that setting a log scale sets good default axis limits + when using pcolor. + """ + x = np.linspace(0, 1, 11) + # Ensuring second x value always falls slightly above 0.1 prevents flakiness with + # numpy v1 #30882. This can be removed once we require numpy >= 2. + x[1] += 0.00001 + y = np.linspace(1, 2, 5) + X, Y = np.meshgrid(x, y) + C = X[:-1, :-1] + Y[:-1, :-1] + + ax = fig_test.subplots() + ax.pcolor(X, Y, C) + ax.set_xscale('log') + + ax = fig_ref.subplots() + ax.pcolor(X, Y, C) + ax.set_xlim(1e-1, 1e0) + ax.set_xscale('log') + + def test_pcolorargs(): n = 12 x = np.linspace(-1.5, 1.5, n) @@ -1575,7 +1619,9 @@ def test_pcolorargs(): x = np.ma.array(x, mask=(x < 0)) with pytest.raises(ValueError): ax.pcolormesh(x, y, Z[:-1, :-1]) - # Expect a warning with non-increasing coordinates + # If the X or Y coords do not possess monotonicity in their respective + # directions, a warning indicating a bad grid will be triggered. + # The case of specifying coordinates by inputting 1D arrays. x = [359, 0, 1] y = [-10, 10] X, Y = np.meshgrid(x, y) @@ -1583,6 +1629,27 @@ def test_pcolorargs(): with pytest.warns(UserWarning, match='are not monotonically increasing or decreasing'): ax.pcolormesh(X, Y, Z, shading='auto') + # The case of specifying coordinates by inputting 2D arrays. + x = np.linspace(-1, 1, 3) + y = np.linspace(-1, 1, 3) + X, Y = np.meshgrid(x, y) + Z = np.zeros(X.shape) + np.random.seed(19680801) + noise_X = np.random.random(X.shape) + noise_Y = np.random.random(Y.shape) + with pytest.warns(UserWarning, + match='are not monotonically increasing or ' + 'decreasing') as record: + # Small perturbations in coordinates will not disrupt the monotonicity + # of the X-coords and Y-coords in their respective directions. + # Therefore, no warnings will be triggered. + ax.pcolormesh(X+noise_X, Y+noise_Y, Z, shading='auto') + assert len(record) == 0 + # Large perturbations have disrupted the monotonicity of the X-coords + # and Y-coords in their respective directions, thus resulting in two + # bad grid warnings. + ax.pcolormesh(X+10*noise_X, Y+10*noise_Y, Z, shading='auto') + assert len(record) == 2 def test_pcolormesh_underflow_error(): @@ -1621,7 +1688,7 @@ def test_pcolorargs_with_read_only(): plt.pcolor(masked_X, masked_Y, masked_Z) -@check_figures_equal(extensions=["png"]) +@check_figures_equal() def test_pcolornearest(fig_test, fig_ref): ax = fig_test.subplots() x = np.arange(0, 10) @@ -1637,7 +1704,7 @@ def test_pcolornearest(fig_test, fig_ref): ax.pcolormesh(x2, y2, Z, shading='nearest') -@check_figures_equal(extensions=["png"]) +@check_figures_equal() def test_pcolornearestunits(fig_test, fig_ref): ax = fig_test.subplots() x = [datetime.datetime.fromtimestamp(x * 3600) for x in range(10)] @@ -1672,7 +1739,7 @@ def test_samesizepcolorflaterror(): @pytest.mark.parametrize('snap', [False, True]) -@check_figures_equal(extensions=["png"]) +@check_figures_equal() def test_pcolorauto(fig_test, fig_ref, snap): ax = fig_test.subplots() x = np.arange(0, 10) @@ -1690,7 +1757,8 @@ def test_pcolorauto(fig_test, fig_ref, snap): ax.pcolormesh(x2, y2, Z, snap=snap) -@image_comparison(['canonical'], tol=0.02 if platform.machine() == 'arm64' else 0) +@image_comparison(['canonical'], + tol=0 if platform.machine() == 'x86_64' else 0.02) def test_canonical(): fig, ax = plt.subplots() ax.plot([1, 2, 3]) @@ -1775,7 +1843,7 @@ def test_marker_as_markerstyle(): ax.errorbar([1, 2, 3], [5, 4, 3], marker=m) -@image_comparison(['markevery'], remove_text=True) +@image_comparison(['markevery.png'], remove_text=True) def test_markevery(): x = np.linspace(0, 10, 100) y = np.sin(x) * np.sqrt(x/10 + 0.5) @@ -1789,7 +1857,7 @@ def test_markevery(): ax.legend() -@image_comparison(['markevery_line'], remove_text=True, tol=0.005) +@image_comparison(['markevery_line.png'], remove_text=True, tol=0.005) def test_markevery_line(): # TODO: a slight change in rendering between Inkscape versions may explain # why one had to introduce a small non-zero tolerance for the SVG test @@ -1807,7 +1875,7 @@ def test_markevery_line(): ax.legend() -@image_comparison(['markevery_linear_scales'], remove_text=True, tol=0.001) +@image_comparison(['markevery_linear_scales.png'], remove_text=True, tol=0.001) def test_markevery_linear_scales(): cases = [None, 8, @@ -1832,7 +1900,7 @@ def test_markevery_linear_scales(): plt.plot(x, y, 'o', ls='-', ms=4, markevery=case) -@image_comparison(['markevery_linear_scales_zoomed'], remove_text=True) +@image_comparison(['markevery_linear_scales_zoomed.png'], remove_text=True) def test_markevery_linear_scales_zoomed(): cases = [None, 8, @@ -1859,7 +1927,7 @@ def test_markevery_linear_scales_zoomed(): plt.ylim((1.1, 1.7)) -@image_comparison(['markevery_log_scales'], remove_text=True) +@image_comparison(['markevery_log_scales.png'], remove_text=True) def test_markevery_log_scales(): cases = [None, 8, @@ -1886,7 +1954,7 @@ def test_markevery_log_scales(): plt.plot(x, y, 'o', ls='-', ms=4, markevery=case) -@image_comparison(['markevery_polar'], style='default', remove_text=True) +@image_comparison(['markevery_polar.png'], style='default', remove_text=True) def test_markevery_polar(): cases = [None, 8, @@ -1910,7 +1978,7 @@ def test_markevery_polar(): plt.plot(theta, r, 'o', ls='-', ms=4, markevery=case) -@image_comparison(['markevery_linear_scales_nans'], remove_text=True) +@image_comparison(['markevery_linear_scales_nans.png'], remove_text=True) def test_markevery_linear_scales_nans(): cases = [None, 8, @@ -1985,7 +2053,7 @@ def test_bar_tick_label_multiple_old_alignment(): align='center') -@check_figures_equal(extensions=["png"]) +@check_figures_equal() def test_bar_decimal_center(fig_test, fig_ref): ax = fig_test.subplots() x0 = [1.5, 8.4, 5.3, 4.2] @@ -1999,7 +2067,7 @@ def test_bar_decimal_center(fig_test, fig_ref): ax.bar(x0, y0, align='center') -@check_figures_equal(extensions=["png"]) +@check_figures_equal() def test_barh_decimal_center(fig_test, fig_ref): ax = fig_test.subplots() x0 = [1.5, 8.4, 5.3, 4.2] @@ -2013,7 +2081,7 @@ def test_barh_decimal_center(fig_test, fig_ref): ax.barh(x0, y0, height=[0.5, 0.5, 1, 1], align='center') -@check_figures_equal(extensions=["png"]) +@check_figures_equal() def test_bar_decimal_width(fig_test, fig_ref): x = [1.5, 8.4, 5.3, 4.2] y = [1.1, 2.2, 3.3, 4.4] @@ -2027,7 +2095,7 @@ def test_bar_decimal_width(fig_test, fig_ref): ax.bar(x, y, width=w0, align='center') -@check_figures_equal(extensions=["png"]) +@check_figures_equal() def test_barh_decimal_height(fig_test, fig_ref): x = [1.5, 8.4, 5.3, 4.2] y = [1.1, 2.2, 3.3, 4.4] @@ -2107,6 +2175,119 @@ def test_bar_datetime_start(): assert isinstance(ax.xaxis.get_major_formatter(), mdates.AutoDateFormatter) +@image_comparison(["grouped_bar.png"], style="mpl20") +def test_grouped_bar(): + data = { + 'data1': [1, 2, 3], + 'data2': [1.2, 2.2, 3.2], + 'data3': [1.4, 2.4, 3.4], + } + + fig, ax = plt.subplots() + ax.grouped_bar(data, tick_labels=['A', 'B', 'C'], + group_spacing=0.5, bar_spacing=0.1, + colors=['#1f77b4', '#58a1cf', '#abd0e6']) + ax.set_yticks([]) + + +@check_figures_equal() +def test_grouped_bar_list_of_datasets(fig_test, fig_ref): + categories = ['A', 'B'] + data1 = [1, 1.2] + data2 = [2, 2.4] + data3 = [3, 3.6] + + ax = fig_test.subplots() + ax.grouped_bar([data1, data2, data3], tick_labels=categories, + labels=["data1", "data2", "data3"]) + ax.legend() + + ax = fig_ref.subplots() + label_pos = np.array([0, 1]) + bar_width = 1 / (3 + 1.5) # 3 bars + 1.5 group_spacing + data_shift = -1 * bar_width + np.array([0, bar_width, 2 * bar_width]) + ax.bar(label_pos + data_shift[0], data1, width=bar_width, label="data1") + ax.bar(label_pos + data_shift[1], data2, width=bar_width, label="data2") + ax.bar(label_pos + data_shift[2], data3, width=bar_width, label="data3") + ax.set_xticks(label_pos, categories) + ax.legend() + + +@check_figures_equal() +def test_grouped_bar_dict_of_datasets(fig_test, fig_ref): + categories = ['A', 'B'] + data_dict = dict(data1=[1, 1.2], data2=[2, 2.4], data3=[3, 3.6]) + + ax = fig_test.subplots() + ax.grouped_bar(data_dict, tick_labels=categories) + ax.legend() + + ax = fig_ref.subplots() + ax.grouped_bar(data_dict.values(), tick_labels=categories, labels=data_dict.keys()) + ax.legend() + + +@check_figures_equal() +def test_grouped_bar_array(fig_test, fig_ref): + categories = ['A', 'B'] + array = np.array([[1, 2, 3], [1.2, 2.4, 3.6]]) + labels = ['data1', 'data2', 'data3'] + + ax = fig_test.subplots() + ax.grouped_bar(array, tick_labels=categories, labels=labels) + ax.legend() + + ax = fig_ref.subplots() + list_of_datasets = [column for column in array.T] + ax.grouped_bar(list_of_datasets, tick_labels=categories, labels=labels) + ax.legend() + + +@check_figures_equal() +def test_grouped_bar_dataframe(fig_test, fig_ref, pd): + categories = ['A', 'B'] + labels = ['data1', 'data2', 'data3'] + df = pd.DataFrame([[1, 2, 3], [1.2, 2.4, 3.6]], + index=categories, columns=labels) + + ax = fig_test.subplots() + ax.grouped_bar(df) + ax.legend() + + ax = fig_ref.subplots() + list_of_datasets = [df[col].to_numpy() for col in df.columns] + ax.grouped_bar(list_of_datasets, tick_labels=categories, labels=labels) + ax.legend() + + +def test_grouped_bar_return_value(): + fig, ax = plt.subplots() + ret = ax.grouped_bar([[1, 2, 3], [11, 12, 13]], tick_labels=['A', 'B', 'C']) + + assert len(ret.bar_containers) == 2 + for bc in ret.bar_containers: + assert isinstance(bc, BarContainer) + assert bc in ax.containers + + ret.remove() + for bc in ret.bar_containers: + assert bc not in ax.containers + + +def test_grouped_bar_hatch_sequence(): + """Each dataset should receive its own hatch pattern when a sequence is passed.""" + fig, ax = plt.subplots() + x = np.arange(2) + heights = [np.array([1, 2]), np.array([2, 3]), np.array([3, 4])] + hatches = ['//', 'xx', '..'] + containers = ax.grouped_bar(heights, positions=x, hatch=hatches) + + # Verify each dataset gets the corresponding hatch + for hatch, c in zip(hatches, containers.bar_containers): + for rect in c: + assert rect.get_hatch() == hatch + + def test_boxplot_dates_pandas(pd): # smoke test for boxplot and dates in pandas data = np.random.rand(5, 2) @@ -2150,9 +2331,8 @@ def test_pcolor_regression(pd): time_axis, y_axis = np.meshgrid(times, y_vals) shape = (len(y_vals) - 1, len(times) - 1) - z_data = np.arange(shape[0] * shape[1]) + z_data = np.arange(shape[0] * shape[1]).reshape(shape) - z_data.shape = shape try: register_matplotlib_converters() @@ -2247,7 +2427,7 @@ def test_pandas_minimal_plot(pd): plt.plot(df, df) -@image_comparison(['hist_log'], remove_text=True) +@image_comparison(['hist_log.png'], remove_text=True) def test_hist_log(): data0 = np.linspace(0, 1, 200)**3 data = np.concatenate([1 - data0, 1 + data0]) @@ -2255,7 +2435,7 @@ def test_hist_log(): ax.hist(data, fill=False, log=True) -@check_figures_equal(extensions=["png"]) +@check_figures_equal() def test_hist_log_2(fig_test, fig_ref): axs_test = fig_test.subplots(2, 3) axs_ref = fig_ref.subplots(2, 3) @@ -2281,6 +2461,21 @@ def test_hist_log_barstacked(): assert axs[0].get_ylim() == axs[1].get_ylim() +def test_hist_timedelta_raises(): + import numpy as np + import matplotlib.pyplot as plt + + fig, ax = plt.subplots() + + arr_np = np.array([1, 2, 5, 7], dtype="timedelta64[D]") + with pytest.raises(TypeError, match="does not currently support timedelta inputs"): + ax.hist(arr_np) + + arr_py = [datetime.timedelta(seconds=i) for i in range(5)] + with pytest.raises(TypeError, match="does not currently support timedelta inputs"): + ax.hist(arr_py) + + @image_comparison(['hist_bar_empty.png'], remove_text=True) def test_hist_bar_empty(): # From #3886: creating hist from empty dataset raises ValueError @@ -2405,6 +2600,15 @@ def test_hist_zorder(histtype, zorder): assert patch.get_zorder() == zorder +def test_hist_single_color_multiple_datasets(): + data = [[0, 1, 2], [3, 4, 5]] + _, _, bar_containers = plt.hist(data, color='k') + for p in bar_containers[0].patches: + assert mcolors.same_color(p.get_facecolor(), 'k') + for p in bar_containers[1].patches: + assert mcolors.same_color(p.get_facecolor(), 'k') + + def test_stairs_no_baseline_fill_warns(): fig, ax = plt.subplots() with pytest.warns(UserWarning, match="baseline=None and fill=True"): @@ -2417,7 +2621,7 @@ def test_stairs_no_baseline_fill_warns(): ) -@check_figures_equal(extensions=['png']) +@check_figures_equal() def test_stairs(fig_test, fig_ref): import matplotlib.lines as mlines y = np.array([6, 14, 32, 37, 48, 32, 21, 4]) # hist @@ -2461,7 +2665,7 @@ def test_stairs(fig_test, fig_ref): ref_axes[5].semilogx() -@check_figures_equal(extensions=['png']) +@check_figures_equal() def test_stairs_fill(fig_test, fig_ref): h, bins = [1, 2, 3, 4, 2], [0, 1, 2, 3, 4, 5] bs = -2 @@ -2487,7 +2691,7 @@ def test_stairs_fill(fig_test, fig_ref): ref_axes[3].set_xlim(bs, None) -@check_figures_equal(extensions=['png']) +@check_figures_equal() def test_stairs_update(fig_test, fig_ref): # fixed ylim because stairs() does autoscale, but updating data does not ylim = -3, 4 @@ -2511,7 +2715,7 @@ def test_stairs_update(fig_test, fig_ref): ref_ax.set_ylim(ylim) -@check_figures_equal(extensions=['png']) +@check_figures_equal() def test_stairs_baseline_None(fig_test, fig_ref): x = np.array([0, 2, 3, 5, 10]) y = np.array([1.148, 1.231, 1.248, 1.25]) @@ -2578,7 +2782,8 @@ def test_stairs_options(): ax.legend(loc=0) -@image_comparison(['test_stairs_datetime.png']) +# TODO: tighten tolerance after baseline image is regenerated for text overhaul +@image_comparison(['test_stairs_datetime.png'], tol=0.2) def test_stairs_datetime(): f, ax = plt.subplots(constrained_layout=True) ax.stairs(np.arange(36), @@ -2587,7 +2792,7 @@ def test_stairs_datetime(): plt.xticks(rotation=30) -@check_figures_equal(extensions=['png']) +@check_figures_equal() def test_stairs_edge_handling(fig_test, fig_ref): # Test test_ax = fig_test.add_subplot() @@ -2611,13 +2816,12 @@ def test_contour_hatching(): x, y, z = contour_dat() fig, ax = plt.subplots() ax.contourf(x, y, z, 7, hatches=['/', '\\', '//', '-'], - cmap=mpl.colormaps['gray'], - extend='both', alpha=0.5) + cmap=mpl.colormaps['gray'].with_alpha(0.5), + extend='both') -@image_comparison( - ['contour_colorbar'], style='mpl20', - tol=0.54 if platform.machine() in ('aarch64', 'arm64', 'ppc64le', 's390x') else 0) +@image_comparison(['contour_colorbar'], style='mpl20', + tol=0 if platform.machine() == 'x86_64' else 0.54) def test_contour_colorbar(): x, y, z = contour_dat() @@ -2639,7 +2843,7 @@ def test_contour_colorbar(): cbar.add_lines(cs2, erase=False) -@image_comparison(['hist2d', 'hist2d'], remove_text=True, style='mpl20') +@image_comparison(['hist2d.png', 'hist2d.png'], remove_text=True, style='mpl20') def test_hist2d(): # Remove this line when this test image is regenerated. plt.rcParams['pcolormesh.snap'] = False @@ -2657,7 +2861,7 @@ def test_hist2d(): ax.hist2d("x", "y", bins=10, data=data, rasterized=True) -@image_comparison(['hist2d_transpose'], remove_text=True, style='mpl20') +@image_comparison(['hist2d_transpose.png'], remove_text=True, style='mpl20') def test_hist2d_transpose(): # Remove this line when this test image is regenerated. plt.rcParams['pcolormesh.snap'] = False @@ -2678,6 +2882,16 @@ def test_hist2d_density(): obj.hist2d(x, y, density=True) +@mpl.style.context("mpl20") +def test_hist2d_autolimits(): + x, y = np.random.random((2, 100)) + ax = plt.figure().add_subplot() + ax.hist2d(x, y) + assert ax.get_xlim() == (x.min(), x.max()) + assert ax.get_ylim() == (y.min(), y.max()) + assert ax.get_autoscale_on() # Autolimits have not been disabled. + + class TestScatter: @image_comparison(['scatter'], style='mpl20', remove_text=True) def test_scatter_plot(self): @@ -2715,7 +2929,7 @@ def test_scatter_marker(self): edgecolors=['k', 'r', 'g', 'b'], marker=verts) - @image_comparison(['scatter_2D'], remove_text=True, extensions=['png']) + @image_comparison(['scatter_2D.png'], remove_text=True) def test_scatter_2D(self): x = np.arange(3) y = np.arange(2) @@ -2724,7 +2938,7 @@ def test_scatter_2D(self): fig, ax = plt.subplots() ax.scatter(x, y, c=z, s=200, edgecolors='face') - @check_figures_equal(extensions=["png"]) + @check_figures_equal() def test_scatter_decimal(self, fig_test, fig_ref): x0 = np.array([1.5, 8.4, 5.3, 4.2]) y0 = np.array([1.1, 2.2, 3.3, 4.4]) @@ -2788,11 +3002,11 @@ def test_scatter_unfillable(self): def test_scatter_size_arg_size(self): x = np.arange(4) - with pytest.raises(ValueError, match='same size as x and y'): + with pytest.raises(ValueError, match='cannot be broadcast to match x and y'): plt.scatter(x, x, x[1:]) - with pytest.raises(ValueError, match='same size as x and y'): + with pytest.raises(ValueError, match='cannot be broadcast to match x and y'): plt.scatter(x[1:], x[1:], x) - with pytest.raises(ValueError, match='float array-like'): + with pytest.raises(ValueError, match='must be float'): plt.scatter(x, x, 'foo') def test_scatter_edgecolor_RGB(self): @@ -2804,11 +3018,10 @@ def test_scatter_edgecolor_RGB(self): edgecolor=(1, 0, 0, 1)) assert mcolors.same_color(coll.get_edgecolor(), (1, 0, 0, 1)) - @check_figures_equal(extensions=["png"]) + @check_figures_equal() def test_scatter_invalid_color(self, fig_test, fig_ref): ax = fig_test.subplots() - cmap = mpl.colormaps["viridis"].resampled(16) - cmap.set_bad("k", 1) + cmap = mpl.colormaps["viridis"].resampled(16).with_extremes(bad="black") # Set a nonuniform size to prevent the last call to `scatter` (plotting # the invalid points separately in fig_ref) from using the marker # stamping fast path, which would result in slightly offset markers. @@ -2820,12 +3033,11 @@ def test_scatter_invalid_color(self, fig_test, fig_ref): ax.scatter([0, 2], [0, 2], c=[1, 2], s=[1, 3], cmap=cmap) ax.scatter([1, 3], [1, 3], s=[2, 4], color="k") - @check_figures_equal(extensions=["png"]) + @check_figures_equal() def test_scatter_no_invalid_color(self, fig_test, fig_ref): # With plotnonfinite=False we plot only 2 points. ax = fig_test.subplots() - cmap = mpl.colormaps["viridis"].resampled(16) - cmap.set_bad("k", 1) + cmap = mpl.colormaps["viridis"].resampled(16).with_extremes(bad="k") ax.scatter(range(4), range(4), c=[1, np.nan, 2, np.nan], s=[1, 2, 3, 4], cmap=cmap, plotnonfinite=False) @@ -2842,14 +3054,14 @@ def test_scatter_norm_vminvmax(self): ax.scatter(x, x, c=x, norm=mcolors.Normalize(-10, 10), vmin=0, vmax=5) - @check_figures_equal(extensions=["png"]) + @check_figures_equal() def test_scatter_single_point(self, fig_test, fig_ref): ax = fig_test.subplots() ax.scatter(1, 1, c=1) ax = fig_ref.subplots() ax.scatter([1], [1], c=[1]) - @check_figures_equal(extensions=["png"]) + @check_figures_equal() def test_scatter_different_shapes(self, fig_test, fig_ref): x = np.arange(10) ax = fig_test.subplots() @@ -2905,7 +3117,7 @@ def test_scatter_different_shapes(self, fig_test, fig_ref): @pytest.mark.parametrize('c_case, re_key', params_test_scatter_c) def test_scatter_c(self, c_case, re_key): - def get_next_color(): + def get_next_color(): # pragma: no cover return 'blue' # currently unused xsize = 4 @@ -2928,7 +3140,7 @@ def get_next_color(): get_next_color_func=get_next_color) @mpl.style.context('default') - @check_figures_equal(extensions=["png"]) + @check_figures_equal() def test_scatter_single_color_c(self, fig_test, fig_ref): rgb = [[1, 0.5, 0.05]] rgba = [[1, 0.5, 0.05, .5]] @@ -2999,7 +3211,7 @@ def _params(c=None, xsize=2, *, edgecolors=None, **kwargs): _result(c=['b', 'g'], colors=np.array([[0, 0, 1, 1], [0, .5, 0, 1]]))), ]) def test_parse_scatter_color_args(params, expected_result): - def get_next_color(): + def get_next_color(): # pragma: no cover return 'blue' # currently unused c, colors, _edgecolors = mpl.axes.Axes._parse_scatter_color_args( @@ -3026,7 +3238,7 @@ def get_next_color(): (dict(color='r', edgecolor='g'), 'g'), ]) def test_parse_scatter_color_args_edgecolors(kwargs, expected_edgecolors): - def get_next_color(): + def get_next_color(): # pragma: no cover return 'blue' # currently unused c = kwargs.pop('c', None) @@ -3038,7 +3250,7 @@ def get_next_color(): def test_parse_scatter_color_args_error(): - def get_next_color(): + def get_next_color(): # pragma: no cover return 'blue' # currently unused with pytest.raises(ValueError, @@ -3048,6 +3260,55 @@ def get_next_color(): c, None, kwargs={}, xsize=2, get_next_color_func=get_next_color) +# Warning message tested in the next two tests. +WARN_MSG = ( + "You passed both c and facecolor/facecolors for the markers. " + "c has precedence over facecolor/facecolors. This behavior may " + "change in the future." +) +# Test cases shared between direct and integration tests +COLOR_TEST_CASES = [ + ('red', 'blue'), + (['red', 'blue'], ['green', 'yellow']), + ([[1, 0, 0], [0, 1, 0]], [[0, 0, 1], [1, 1, 0]]) +] + + +@pytest.mark.parametrize('c, facecolor', COLOR_TEST_CASES) +def test_parse_c_facecolor_warning_direct(c, facecolor): + """Test the internal _parse_scatter_color_args method directly.""" + def get_next_color(): # pragma: no cover + return 'blue' # currently unused + + # Test with facecolors (plural) + with pytest.warns(UserWarning, match=WARN_MSG): + mpl.axes.Axes._parse_scatter_color_args( + c=c, edgecolors=None, kwargs={'facecolors': facecolor}, + xsize=2, get_next_color_func=get_next_color) + + # Test with facecolor (singular) + with pytest.warns(UserWarning, match=WARN_MSG): + mpl.axes.Axes._parse_scatter_color_args( + c=c, edgecolors=None, kwargs={'facecolor': facecolor}, + xsize=2, get_next_color_func=get_next_color) + + +@pytest.mark.parametrize('c, facecolor', COLOR_TEST_CASES) +def test_scatter_c_facecolor_warning_integration(c, facecolor): + """Test the warning through the actual scatter plot creation.""" + fig, ax = plt.subplots() + x = [0, 1] if isinstance(c, (list, tuple)) else [0] + y = x + + # Test with facecolors (plural) + with pytest.warns(UserWarning, match=WARN_MSG): + ax.scatter(x, y, c=c, facecolors=facecolor) + + # Test with facecolor (singular) + with pytest.warns(UserWarning, match=WARN_MSG): + ax.scatter(x, y, c=c, facecolor=facecolor) + + def test_as_mpl_axes_api(): # tests the _as_mpl_axes api class Polar: @@ -3145,8 +3406,8 @@ def test_log_scales_invalid(): ax.set_ylim(-1, 10) -@image_comparison(['stackplot_test_image', 'stackplot_test_image'], - tol=0.031 if platform.machine() == 'arm64' else 0) +@image_comparison(['stackplot_test_image.png', 'stackplot_test_image.png'], + tol=0 if platform.machine() == 'x86_64' else 0.031) def test_stackplot(): fig = plt.figure() x = np.linspace(0, 10, 10) @@ -3155,19 +3416,19 @@ def test_stackplot(): y3 = 3.0 * x + 2 ax = fig.add_subplot(1, 1, 1) ax.stackplot(x, y1, y2, y3) - ax.set_xlim((0, 10)) - ax.set_ylim((0, 70)) + ax.set_xlim(0, 10) + ax.set_ylim(0, 70) # Reuse testcase from above for a test with labeled data and with colours # from the Axes property cycle. data = {"x": x, "y1": y1, "y2": y2, "y3": y3} fig, ax = plt.subplots() ax.stackplot("x", "y1", "y2", "y3", data=data, colors=["C0", "C1", "C2"]) - ax.set_xlim((0, 10)) - ax.set_ylim((0, 70)) + ax.set_xlim(0, 10) + ax.set_ylim(0, 70) -@image_comparison(['stackplot_test_baseline'], remove_text=True) +@image_comparison(['stackplot_test_baseline.png'], remove_text=True) def test_stackplot_baseline(): np.random.seed(0) @@ -3201,16 +3462,50 @@ def test_stackplot_hatching(fig_ref, fig_test): # stackplot with different hatching styles (issue #27146) ax_test = fig_test.subplots() ax_test.stackplot(x, y1, y2, y3, hatch=["x", "//", "\\\\"], colors=["white"]) - ax_test.set_xlim((0, 10)) - ax_test.set_ylim((0, 70)) + ax_test.set_xlim(0, 10) + ax_test.set_ylim(0, 70) # compare with result from hatching each layer individually stack_baseline = np.zeros(len(x)) ax_ref = fig_ref.subplots() ax_ref.fill_between(x, stack_baseline, y1, hatch="x", facecolor="white") ax_ref.fill_between(x, y1, y1+y2, hatch="//", facecolor="white") ax_ref.fill_between(x, y1+y2, y1+y2+y3, hatch="\\\\", facecolor="white") - ax_ref.set_xlim((0, 10)) - ax_ref.set_ylim((0, 70)) + ax_ref.set_xlim(0, 10) + ax_ref.set_ylim(0, 70) + + +def test_stackplot_facecolor(): + # Test that facecolors are properly passed and take precedence over colors parameter + x = np.linspace(0, 10, 10) + y1 = 1.0 * x + y2 = 2.0 * x + 1 + + facecolors = ['r', 'b'] + + fig, ax = plt.subplots() + + colls = ax.stackplot(x, y1, y2, facecolor=facecolors, colors=['c', 'm']) + for coll, fcolor in zip(colls, facecolors): + assert mcolors.same_color(coll.get_facecolor(), fcolor) + + # Plural alias should also work + colls = ax.stackplot(x, y1, y2, facecolors=facecolors, colors=['c', 'm']) + for coll, fcolor in zip(colls, facecolors): + assert mcolors.same_color(coll.get_facecolor(), fcolor) + + +def test_stackplot_subfig_legend(): + # Smoke test for https://github.com/matplotlib/matplotlib/issues/30158 + + fig = plt.figure() + subfigs = fig.subfigures(nrows=1, ncols=2) + + for _fig in subfigs: + ax = _fig.subplots(nrows=1, ncols=1) + ax.stackplot([3, 4], [[1, 2]], labels=['a']) + + fig.legend() + fig.draw_without_rendering() def _bxp_test_helper( @@ -3477,7 +3772,7 @@ def test_bxp_bad_capwidths(): _bxp_test_helper(bxp_kwargs=dict(capwidths=[1])) -@image_comparison(['boxplot', 'boxplot'], tol=1.28, style='default') +@image_comparison(['boxplot.png', 'boxplot.png'], tol=1.28, style='default') def test_boxplot(): # Randomness used for bootstrapping. np.random.seed(937) @@ -3487,16 +3782,16 @@ def test_boxplot(): fig, ax = plt.subplots() ax.boxplot([x, x], bootstrap=10000, notch=1) - ax.set_ylim((-30, 30)) + ax.set_ylim(-30, 30) # Reuse testcase from above for a labeled data test data = {"x": [x, x]} fig, ax = plt.subplots() ax.boxplot("x", bootstrap=10000, notch=1, data=data) - ax.set_ylim((-30, 30)) + ax.set_ylim(-30, 30) -@check_figures_equal(extensions=["png"]) +@check_figures_equal() def test_boxplot_masked(fig_test, fig_ref): # Check that masked values are ignored when plotting a boxplot x_orig = np.linspace(-1, 1, 200) @@ -3531,10 +3826,10 @@ def test_boxplot_sym2(): fig, [ax1, ax2] = plt.subplots(1, 2) ax1.boxplot([x, x], bootstrap=10000, sym='^') - ax1.set_ylim((-30, 30)) + ax1.set_ylim(-30, 30) ax2.boxplot([x, x], bootstrap=10000, sym='g') - ax2.set_ylim((-30, 30)) + ax2.set_ylim(-30, 30) @image_comparison(['boxplot_sym.png'], @@ -3547,7 +3842,7 @@ def test_boxplot_sym(): fig, ax = plt.subplots() ax.boxplot([x, x], sym='gs') - ax.set_ylim((-30, 30)) + ax.set_ylim(-30, 30) @image_comparison(['boxplot_autorange_false_whiskers.png', @@ -3562,11 +3857,11 @@ def test_boxplot_autorange_whiskers(): fig1, ax1 = plt.subplots() ax1.boxplot([x, x], bootstrap=10000, notch=1) - ax1.set_ylim((-5, 5)) + ax1.set_ylim(-5, 5) fig2, ax2 = plt.subplots() ax2.boxplot([x, x], bootstrap=10000, notch=1, autorange=True) - ax2.set_ylim((-5, 5)) + ax2.set_ylim(-5, 5) def _rc_test_bxp_helper(ax, rc_dict): @@ -3577,7 +3872,7 @@ def _rc_test_bxp_helper(ax, rc_dict): return ax -@image_comparison(['boxplot_rc_parameters'], +@image_comparison(['boxplot_rc_parameters.png'], savefig_kwarg={'dpi': 100}, remove_text=True, tol=1, style='default') def test_boxplot_rc_parameters(): @@ -3656,7 +3951,7 @@ def test_boxplot_with_CIarray(): # another with manual values ax.boxplot([x, x], bootstrap=10000, usermedians=[None, 1.0], conf_intervals=CIs, notch=1) - ax.set_ylim((-30, 30)) + ax.set_ylim(-30, 30) @image_comparison(['boxplot_no_inverted_whisker.png'], @@ -3891,6 +4186,85 @@ def test_violinplot_sides(): showextrema=True, showmedians=True, side=side) +def violin_plot_stats(): + datetimes = [ + datetime.datetime(2023, 2, 10), + datetime.datetime(2023, 5, 18), + datetime.datetime(2023, 6, 6) + ] + return [{ + 'coords': datetimes, + 'vals': [1.2, 2.8, 1.5], + 'mean': 1.84, + 'median': 1.5, + 'min': 1.2, + 'max': 2.8, + 'quantiles': [1.2, 1.5, 2.8] + }, { + 'coords': datetimes, + 'vals': [0.8, 1.1, 0.9], + 'mean': 0.94, + 'median': 0.9, + 'min': 0.8, + 'max': 1.1, + 'quantiles': [0.8, 0.9, 1.1] + }] + + +def test_datetime_positions_with_datetime64(): + """Test that datetime positions with float widths raise TypeError.""" + fig, ax = plt.subplots() + positions = [np.datetime64('2020-01-01'), np.datetime64('2021-01-01')] + widths = [0.5, 1.0] + with pytest.raises(TypeError, + match=("np.datetime64 'position' values require " + "np.timedelta64 'widths'")): + ax.violin(violin_plot_stats(), positions=positions, widths=widths) + + +def test_datetime_positions_with_float_widths_raises(): + """Test that datetime positions with float widths raise TypeError.""" + fig, ax = plt.subplots() + positions = [datetime.datetime(2020, 1, 1), datetime.datetime(2021, 1, 1)] + widths = [0.5, 1.0] + with pytest.raises(TypeError, + match=("datetime/date 'position' values require " + "timedelta 'widths'")): + ax.violin(violin_plot_stats(), positions=positions, widths=widths) + + +def test_datetime_positions_with_scalar_float_width_raises(): + """Test that datetime positions with scalar float width raise TypeError.""" + fig, ax = plt.subplots() + positions = [datetime.datetime(2020, 1, 1), datetime.datetime(2021, 1, 1)] + widths = 0.75 + with pytest.raises(TypeError, + match=("datetime/date 'position' values require " + "timedelta 'widths'")): + ax.violin(violin_plot_stats(), positions=positions, widths=widths) + + +def test_numeric_positions_with_float_widths_ok(): + """Test that numeric positions with float widths work.""" + fig, ax = plt.subplots() + positions = [1.0, 2.0] + widths = [0.5, 1.0] + ax.violin(violin_plot_stats(), positions=positions, widths=widths) + + +def test_mixed_positions_datetime_and_numeric_raises(): + """Test that mixed datetime and numeric positions + with float widths raise TypeError. + """ + fig, ax = plt.subplots() + positions = [datetime.datetime(2020, 1, 1), 2.0] + widths = [0.5, 1.0] + with pytest.raises(TypeError, + match=("datetime/date 'position' values require " + "timedelta 'widths'")): + ax.violin(violin_plot_stats(), positions=positions, widths=widths) + + def test_violinplot_bad_positions(): ax = plt.axes() # First 9 digits of frac(sqrt(47)) @@ -3935,7 +4309,111 @@ def test_violinplot_outofrange_quantiles(): ax.violinplot(data, quantiles=[[-0.05, 0.2, 0.3, 0.75]]) -@check_figures_equal(extensions=["png"]) +@check_figures_equal() +def test_violinplot_color_specification(fig_test, fig_ref): + # Ensures that setting colors in violinplot constructor works + # the same way as setting the color of each object manually + np.random.seed(19680801) + data = [sorted(np.random.normal(0, std, 100)) for std in range(1, 4)] + kwargs = {'showmeans': True, + 'showextrema': True, + 'showmedians': True + } + + def color_violins(parts, facecolor=None, linecolor=None): + """Helper to color parts manually.""" + if facecolor is not None: + for pc in parts['bodies']: + pc.set_facecolor(facecolor) + # disable alpha Artist property to counter the legacy behavior + # that applies an alpha of 0.3 to the bodies if no facecolor + # was set + pc.set_alpha(None) + if linecolor is not None: + for partname in ('cbars', 'cmins', 'cmaxes', 'cmeans', 'cmedians'): + if partname in parts: + lc = parts[partname] + lc.set_edgecolor(linecolor) + + # Reference image + ax = fig_ref.subplots(1, 3) + parts0 = ax[0].violinplot(data, **kwargs) + parts1 = ax[1].violinplot(data, **kwargs) + parts2 = ax[2].violinplot(data, **kwargs) + + color_violins(parts0, facecolor=('r', 0.5), linecolor=('r', 0.2)) + color_violins(parts1, facecolor='r') + color_violins(parts2, linecolor='r') + + # Test image + ax = fig_test.subplots(1, 3) + ax[0].violinplot(data, facecolor=('r', 0.5), linecolor=('r', 0.2), **kwargs) + ax[1].violinplot(data, facecolor='r', **kwargs) + ax[2].violinplot(data, linecolor='r', **kwargs) + + +def test_violinplot_color_sequence(): + # Ensures that setting a sequence of colors works the same as setting + # each color independently + np.random.seed(19680801) + data = [sorted(np.random.normal(0, std, 100)) for std in range(1, 5)] + kwargs = {'showmeans': True, 'showextrema': True, 'showmedians': True} + + def assert_colors_equal(colors1, colors2): + assert all(mcolors.same_color(c1, c2) + for c1, c2 in zip(colors1, colors2)) + + # Color sequence + N = len(data) + positions = range(N) + facecolors = ['k', 'r', ('b', 0.5), ('g', 0.2)] + linecolors = [('y', 0.4), 'b', 'm', ('k', 0.8)] + + # Test image + fig_test = plt.figure() + ax = fig_test.gca() + parts_test = ax.violinplot(data, + positions=positions, + facecolor=facecolors, + linecolor=linecolors, + **kwargs) + + body_colors = [p.get_facecolor() for p in parts_test["bodies"]] + assert_colors_equal(body_colors, mcolors.to_rgba_array(facecolors)) + + for part in ["cbars", "cmins", "cmaxes", "cmeans", "cmedians"]: + colors_test = parts_test[part].get_edgecolor() + assert_colors_equal(colors_test, mcolors.to_rgba_array(linecolors)) + + +def test_violinplot_alpha(): + matplotlib.style.use('default') + data = [(np.random.normal(0, 1, 100))] + + fig, ax = plt.subplots() + parts = ax.violinplot(data, positions=[1]) + + # Case 1: If facecolor is unspecified, it's the first color from the color cycle + # with Artist-level alpha=0.3 + facecolor = ('y' if mpl.rcParams['_internal.classic_mode'] + else plt.rcParams['axes.prop_cycle'].by_key()['color'][0]) + assert mcolors.same_color(parts['bodies'][0].get_facecolor(), (facecolor, 0.3)) + assert parts['bodies'][0].get_alpha() == 0.3 + # setting a new facecolor maintains the alpha + parts['bodies'][0].set_facecolor('red') + assert mcolors.same_color(parts['bodies'][0].get_facecolor(), ('red', 0.3)) + + # Case 2: If facecolor is explicitly given, it's alpha does not become an + # Artist property + parts = ax.violinplot(data, positions=[1], facecolor=('blue', 0.3)) + assert mcolors.same_color(parts['bodies'][0].get_facecolor(), ('blue', 0.3)) + assert parts['bodies'][0].get_alpha() is None + # so setting a new color does not maintain the alpha + parts['bodies'][0].set_facecolor('red') + assert mcolors.same_color(parts['bodies'][0].get_facecolor(), 'red') + + +@check_figures_equal() def test_violinplot_single_list_quantiles(fig_test, fig_ref): # Ensures quantile list for 1D can be passed in as single list # First 9 digits of frac(sqrt(83)) @@ -3951,7 +4429,7 @@ def test_violinplot_single_list_quantiles(fig_test, fig_ref): ax.violinplot(data, quantiles=[[0.1, 0.3, 0.9]]) -@check_figures_equal(extensions=["png"]) +@check_figures_equal() def test_violinplot_pandas_series(fig_test, fig_ref, pd): np.random.seed(110433579) s1 = pd.Series(np.random.normal(size=7), index=[9, 8, 7, 6, 5, 4, 3]) @@ -3992,7 +4470,7 @@ def test_tick_space_size_0(): plt.savefig(b, dpi=80, format='raw') -@image_comparison(['errorbar_basic', 'errorbar_mixed', 'errorbar_basic']) +@image_comparison(['errorbar_basic.png', 'errorbar_mixed.png', 'errorbar_basic.png']) def test_errorbar(): # longdouble due to floating point rounding issues with certain # computer chipsets @@ -4047,8 +4525,7 @@ def test_errorbar(): ax.set_title("Simplest errorbars, 0.2 in x, 0.4 in y") -@image_comparison(['mixed_errorbar_polar_caps'], extensions=['png'], - remove_text=True) +@image_comparison(['mixed_errorbar_polar_caps.png'], remove_text=True) def test_mixed_errorbar_polar_caps(): """ Mix several polar errorbar use cases in a single test figure. @@ -4130,7 +4607,7 @@ def test_errorbar_shape(): ax.errorbar(x, y, yerr=yerr, xerr=xerr, fmt='o') -@image_comparison(['errorbar_limits']) +@image_comparison(['errorbar_limits.png']) def test_errorbar_limits(): x = np.arange(0.5, 5.5, 0.5) y = np.exp(-x) @@ -4173,7 +4650,7 @@ def test_errorbar_limits(): xlolims=xlolims, xuplims=xuplims, uplims=uplims, lolims=lolims, ls='none', mec='blue', capsize=0, color='cyan') - ax.set_xlim((0, 5.5)) + ax.set_xlim(0, 5.5) ax.set_title('Errorbar upper and lower limits') @@ -4188,6 +4665,24 @@ def test_errorbar_nonefmt(): assert np.all(errbar.get_color() == mcolors.to_rgba('C0')) +def test_errorbar_remove(): + x = np.arange(5) + y = np.arange(5) + + fig, ax = plt.subplots() + ec = ax.errorbar(x, y, xerr=1, yerr=1) + + assert len(ax.containers) == 1 + assert len(ax.lines) == 5 + assert len(ax.collections) == 2 + + ec.remove() + + assert not ax.containers + assert not ax.lines + assert not ax.collections + + def test_errorbar_line_specific_kwargs(): # Check that passing line-specific keyword arguments will not result in # errors. @@ -4205,7 +4700,7 @@ def test_errorbar_line_specific_kwargs(): assert plotline.get_drawstyle() == 'steps-mid' -@check_figures_equal(extensions=['png']) +@check_figures_equal() def test_errorbar_with_prop_cycle(fig_test, fig_ref): ax = fig_ref.subplots() ax.errorbar(x=[2, 4, 10], y=[0, 1, 2], yerr=0.5, @@ -4329,19 +4824,40 @@ def test_errorbar_linewidth_type(elinewidth): plt.errorbar([1, 2, 3], [1, 2, 3], yerr=[1, 2, 3], elinewidth=elinewidth) -@check_figures_equal(extensions=["png"]) +def test_errorbar_linestyle_type(): + eb = plt.errorbar([1, 2, 3], [1, 2, 3], + yerr=[1, 2, 3], elinestyle='--') + errorlines = eb[-1][0] + errorlinestyle = errorlines.get_linestyle() + assert errorlinestyle == [(0, (6, 6))] + + +@check_figures_equal() def test_errorbar_nan(fig_test, fig_ref): ax = fig_test.add_subplot() xs = range(5) ys = np.array([1, 2, np.nan, np.nan, 3]) es = np.array([4, 5, np.nan, np.nan, 6]) - ax.errorbar(xs, ys, es) + ax.errorbar(xs, ys, yerr=es) ax = fig_ref.add_subplot() - ax.errorbar([0, 1], [1, 2], [4, 5]) - ax.errorbar([4], [3], [6], fmt="C0") + ax.errorbar([0, 1], [1, 2], yerr=[4, 5]) + ax.errorbar([4], [3], yerr=[6], fmt="C0") + +@check_figures_equal() +def test_errorbar_masked_negative(fig_test, fig_ref): + ax = fig_test.add_subplot() + xs = range(5) + mask = np.array([False, False, True, True, False]) + ys = np.ma.array([1, 2, 2, 2, 3], mask=mask) + es = np.ma.array([4, 5, -1, -10, 6], mask=mask) + ax.errorbar(xs, ys, yerr=es) + ax = fig_ref.add_subplot() + ax.errorbar([0, 1], [1, 2], yerr=[4, 5]) + ax.errorbar([4], [3], yerr=[6], fmt="C0") -@image_comparison(['hist_stacked_stepfilled', 'hist_stacked_stepfilled']) + +@image_comparison(['hist_stacked_stepfilled.png', 'hist_stacked_stepfilled.png']) def test_hist_stacked_stepfilled(): # make some data d1 = np.linspace(1, 3, 20) @@ -4355,7 +4871,7 @@ def test_hist_stacked_stepfilled(): ax.hist("x", histtype="stepfilled", stacked=True, data=data) -@image_comparison(['hist_offset']) +@image_comparison(['hist_offset.png']) def test_hist_offset(): # make some data d1 = np.linspace(0, 10, 50) @@ -4384,7 +4900,7 @@ def test_hist_step_horiz(): ax.hist((d1, d2), histtype="step", orientation="horizontal") -@image_comparison(['hist_stacked_weights']) +@image_comparison(['hist_stacked_weights.png']) def test_hist_stacked_weighted(): # make some data d1 = np.linspace(0, 10, 50) @@ -4396,14 +4912,12 @@ def test_hist_stacked_weighted(): @image_comparison(['stem.png'], style='mpl20', remove_text=True) -def test_stem(): +def test_stem(text_placeholders): x = np.linspace(0.1, 2 * np.pi, 100) fig, ax = plt.subplots() - # Label is a single space to force a legend to be drawn, but to avoid any - # text being drawn ax.stem(x, np.cos(x), - linefmt='C2-.', markerfmt='k+', basefmt='C1-.', label=' ') + linefmt='C2-.', markerfmt='k+', basefmt='C1-.', label='stem') ax.legend() @@ -4428,6 +4942,11 @@ def _assert_equal(stem_container, expected): _assert_equal(ax.stem(y, linefmt='r--'), expected=([0, 1, 2], y)) _assert_equal(ax.stem(y, 'r--'), expected=([0, 1, 2], y)) + with pytest.raises(ValueError): + ax.stem([[y]]) + with pytest.raises(ValueError): + ax.stem([[x]], y) + def test_stem_markerfmt(): """Test that stem(..., markerfmt=...) produces the intended markers.""" @@ -4512,7 +5031,18 @@ def test_stem_orientation(): orientation='horizontal') -@image_comparison(['hist_stacked_stepfilled_alpha']) +def test_stem_polar_baseline(): + """Test that the baseline is interpolated so that it will follow the radius.""" + fig = plt.figure() + ax = fig.add_subplot(projection='polar') + x = np.linspace(1.57, 3.14, 10) + y = np.linspace(0, 1, 10) + bottom = 0.5 + container = ax.stem(x, y, bottom=bottom) + assert container.baseline.get_path()._interpolation_steps > 100 + + +@image_comparison(['hist_stacked_stepfilled_alpha.png']) def test_hist_stacked_stepfilled_alpha(): # make some data d1 = np.linspace(1, 3, 20) @@ -4521,7 +5051,7 @@ def test_hist_stacked_stepfilled_alpha(): ax.hist((d1, d2), histtype="stepfilled", stacked=True, alpha=0.5) -@image_comparison(['hist_stacked_step']) +@image_comparison(['hist_stacked_step.png']) def test_hist_stacked_step(): # make some data d1 = np.linspace(1, 3, 20) @@ -4530,7 +5060,7 @@ def test_hist_stacked_step(): ax.hist((d1, d2), histtype="step", stacked=True) -@image_comparison(['hist_stacked_normed']) +@image_comparison(['hist_stacked_normed.png']) def test_hist_stacked_density(): # make some data d1 = np.linspace(1, 3, 20) @@ -4618,7 +5148,7 @@ def test_hist_stacked_step_bottom_geometry(): assert_array_equal(polygon.get_xy(), xy[1]) -@image_comparison(['hist_stacked_bar']) +@image_comparison(['hist_stacked_bar.png']) def test_hist_stacked_bar(): # make some data d = [[100, 100, 100, 100, 200, 320, 450, 80, 20, 600, 310, 800], @@ -4642,7 +5172,7 @@ def test_hist_stacked_bar(): {'linestyle': ["-", "--", ":"]}, {'linewidth': [1, 1.5, 2]}, {'color': ["b", "g", "r"]})) -@check_figures_equal(extensions=["png"]) +@check_figures_equal() def test_hist_vectorized_params(fig_test, fig_ref, kwargs): np.random.seed(19680801) xs = [np.random.randn(n) for n in [20, 50, 100]] @@ -4755,7 +5285,7 @@ def test_rgba_markers(): ax.axis([-1, 4, 0, 5]) -@image_comparison(['mollweide_grid'], remove_text=True) +@image_comparison(['mollweide_grid.png'], remove_text=True) def test_mollweide_grid(): # test that both horizontal and vertical gridlines appear on the Mollweide # projection @@ -4838,7 +5368,7 @@ def test_alpha(): markersize=20, lw=10) -@image_comparison(['eventplot', 'eventplot'], remove_text=True) +@image_comparison(['eventplot.png', 'eventplot.png'], remove_text=True) def test_eventplot(): np.random.seed(0) @@ -4990,7 +5520,7 @@ def test_eventplot_orientation(data, orientation): plt.draw() -@check_figures_equal(extensions=['png']) +@check_figures_equal() def test_eventplot_units_list(fig_test, fig_ref): # test that list of lists converted properly: ts_1 = [datetime.datetime(2021, 1, 1), datetime.datetime(2021, 1, 2), @@ -5022,7 +5552,7 @@ def test_marker_styles(): @image_comparison(['rc_markerfill.png'], - tol=0.037 if platform.machine() == 'arm64' else 0) + tol=0 if platform.machine() == 'x86_64' else 0.037) def test_markers_fillstyle_rcparams(): fig, ax = plt.subplots() x = np.arange(7) @@ -5040,11 +5570,11 @@ def test_vertex_markers(): fig, ax = plt.subplots() ax.plot(data, linestyle='', marker=marker_as_tuple, mfc='k') ax.plot(data[::-1], linestyle='', marker=marker_as_list, mfc='b') - ax.set_xlim([-1, 10]) - ax.set_ylim([-1, 10]) + ax.set_xlim(-1, 10) + ax.set_ylim(-1, 10) -@image_comparison(['vline_hline_zorder', 'errorbar_zorder'], +@image_comparison(['vline_hline_zorder.png', 'errorbar_zorder.png'], tol=0 if platform.machine() == 'x86_64' else 0.026) def test_eb_line_zorder(): x = list(range(10)) @@ -5167,8 +5697,7 @@ def test_axline_args(): plt.draw() -@image_comparison(['vlines_basic', 'vlines_with_nan', 'vlines_masked'], - extensions=['png']) +@image_comparison(['vlines_basic.png', 'vlines_with_nan.png', 'vlines_masked.png']) def test_vlines(): # normal x1 = [2, 3, 4, 5, 7] @@ -5214,8 +5743,7 @@ def test_vlines_default(): assert mpl.colors.same_color(lines.get_color(), 'red') -@image_comparison(['hlines_basic', 'hlines_with_nan', 'hlines_masked'], - extensions=['png']) +@image_comparison(['hlines_basic.png', 'hlines_with_nan.png', 'hlines_masked.png']) def test_hlines(): # normal y1 = [2, 3, 4, 5, 7] @@ -5263,7 +5791,7 @@ def test_hlines_default(): @pytest.mark.parametrize('data', [[1, 2, 3, np.nan, 5], np.ma.masked_equal([1, 2, 3, 4, 5], 4)]) -@check_figures_equal(extensions=["png"]) +@check_figures_equal() def test_lines_with_colors(fig_test, fig_ref, data): test_colors = ['red', 'green', 'blue', 'purple', 'orange'] fig_test.add_subplot(2, 1, 1).vlines(data, 0, 1, @@ -5279,8 +5807,7 @@ def test_lines_with_colors(fig_test, fig_ref, data): colors=expect_color, linewidth=5) -@image_comparison(['vlines_hlines_blended_transform'], - extensions=['png'], style='mpl20') +@image_comparison(['vlines_hlines_blended_transform.png'], style='mpl20') def test_vlines_hlines_blended_transform(): t = np.arange(5.0, 10.0, 0.1) s = np.exp(-t) + np.sin(2 * np.pi * t) + 10 @@ -5310,8 +5837,8 @@ def test_step_linestyle(): ax.step(x, y, lw=5, linestyle=ls, where='pre') ax.step(x, y + 1, lw=5, linestyle=ls, where='mid') ax.step(x, y + 2, lw=5, linestyle=ls, where='post') - ax.set_xlim([-1, 5]) - ax.set_ylim([-1, 7]) + ax.set_xlim(-1, 5) + ax.set_ylim(-1, 7) # Reuse testcase from above for a labeled data test data = {"X": x, "Y0": y, "Y1": y+1, "Y2": y+2} @@ -5322,8 +5849,8 @@ def test_step_linestyle(): ax.step("X", "Y0", lw=5, linestyle=ls, where='pre', data=data) ax.step("X", "Y1", lw=5, linestyle=ls, where='mid', data=data) ax.step("X", "Y2", lw=5, linestyle=ls, where='post', data=data) - ax.set_xlim([-1, 5]) - ax.set_ylim([-1, 7]) + ax.set_xlim(-1, 5) + ax.set_ylim(-1, 7) @image_comparison(['mixed_collection'], remove_text=True) @@ -5476,7 +6003,7 @@ def test_specgram_fs_none(): assert xmin == 32 and xmax == 96 -@check_figures_equal(extensions=["png"]) +@check_figures_equal() def test_specgram_origin_rcparam(fig_test, fig_ref): """Test specgram ignores image.origin rcParam and uses origin 'upper'.""" t = np.arange(500) @@ -5589,7 +6116,7 @@ def test_psd_csd_edge_cases(): axs[1].csd(np.zeros(5), np.zeros(5)) -@check_figures_equal(extensions=['png']) +@check_figures_equal() def test_twin_remove(fig_test, fig_ref): ax_test = fig_test.add_subplot() ax_twinx = ax_test.twinx() @@ -5605,7 +6132,7 @@ def test_twin_remove(fig_test, fig_ref): @image_comparison(['twin_spines.png'], remove_text=True, - tol=0.022 if platform.machine() == 'arm64' else 0) + tol=0 if platform.machine() == 'x86_64' else 0.022) def test_twin_spines(): def make_patch_spines_invisible(ax): @@ -5712,6 +6239,21 @@ def test_grid(): assert not ax.xaxis.majorTicks[0].gridline.get_visible() +def test_grid_color_with_alpha(): + """Test that grid(color=(..., alpha)) respects the alpha value.""" + fig, ax = plt.subplots() + ax.grid(True, color=(0.5, 0.6, 0.7, 0.3)) + + # Check that alpha is extracted from color tuple + for tick in ax.xaxis.get_major_ticks(): + assert tick.gridline.get_alpha() == 0.3, \ + f"Expected alpha=0.3, got {tick.gridline.get_alpha()}" + + for tick in ax.yaxis.get_major_ticks(): + assert tick.gridline.get_alpha() == 0.3, \ + f"Expected alpha=0.3, got {tick.gridline.get_alpha()}" + + def test_reset_grid(): fig, ax = plt.subplots() ax.tick_params(reset=True, which='major', labelsize=10) @@ -5725,7 +6267,7 @@ def test_reset_grid(): assert ax.xaxis.majorTicks[0].gridline.get_visible() -@check_figures_equal(extensions=['png']) +@check_figures_equal() def test_reset_ticks(fig_test, fig_ref): for fig in [fig_ref, fig_test]: ax = fig.add_subplot() @@ -5961,8 +6503,8 @@ def test_pie_default(): autopct='%1.1f%%', shadow=True, startangle=90) -@image_comparison(['pie_linewidth_0', 'pie_linewidth_0', 'pie_linewidth_0'], - extensions=['png'], style='mpl20', tol=0.01) +@image_comparison(['pie_linewidth_0.png', 'pie_linewidth_0.png', 'pie_linewidth_0.png'], + style='mpl20', tol=0.01) def test_pie_linewidth_0(): # The slices will be ordered and plotted counter-clockwise. labels = 'Frogs', 'Hogs', 'Dogs', 'Logs' @@ -6071,7 +6613,8 @@ def test_pie_frame_grid(): plt.axis('equal') -@image_comparison(['pie_rotatelabels_true.png'], style='mpl20', tol=0.009) +# TODO: tighten tolerance after baseline image is regenerated for text overhaul +@image_comparison(['pie_rotatelabels_true.png'], style='mpl20', tol=0.1) def test_pie_rotatelabels_true(): # The slices will be ordered and plotted counter-clockwise. labels = 'Hogwarts', 'Frogs', 'Dogs', 'Logs' @@ -6201,8 +6744,59 @@ def test_pie_hatch_multi(fig_test, fig_ref): [w.set_hatch(hp) for w, hp in zip(wedges, hatch)] +def test_pie_label_formatter(): + fig, ax = plt.subplots() + pie = ax.pie([2, 3]) + + texts = ax.pie_label(pie, '{absval:03d}') + assert texts[0].get_text() == '002' + assert texts[1].get_text() == '003' + + texts = ax.pie_label(pie, '{frac:.1%}') + assert texts[0].get_text() == '40.0%' + assert texts[1].get_text() == '60.0%' + + +@pytest.mark.parametrize('distance', [0.6, 1.1]) +@pytest.mark.parametrize('rotate', [False, True]) +def test_pie_label_auto_align(distance, rotate): + fig, ax = plt.subplots() + pie = ax.pie([1, 1], startangle=45) + + texts = ax.pie_label( + pie, ['spam', 'eggs'], distance=distance, rotate=rotate, alignment='auto') + + if distance < 1: + for text in texts: + # labels within the pie should be centered + assert text.get_horizontalalignment() == 'center' + assert text.get_verticalalignment() == 'center' + + else: + # labels outside the pie should be aligned away from it + h_expected = ['right', 'left'] + v_expected = ['bottom', 'top'] + for text, h_align, v_align in zip(texts, h_expected, v_expected): + assert text.get_horizontalalignment() == h_align + if rotate: + assert text.get_verticalalignment() == v_align + else: + assert text.get_verticalalignment() == 'center' + + +def test_pie_label_fail(): + sizes = 15, 30, 45, 10 + labels = 'Frogs', 'Hogs' + fig, ax = plt.subplots() + pie = ax.pie(sizes) + + match = re.escape("The number of labels (2) must match the number of wedges (4)") + with pytest.raises(ValueError, match=match): + ax.pie_label(pie, labels) + + @image_comparison(['set_get_ticklabels.png'], - tol=0.025 if platform.machine() == 'arm64' else 0) + tol=0 if platform.machine() == 'x86_64' else 0.025) def test_set_get_ticklabels(): # test issue 2246 fig, ax = plt.subplots(2) @@ -6239,7 +6833,7 @@ def test_set_ticks_kwargs_raise_error_without_labels(): ax.xaxis.set_ticks(ticks, alpha=0.5) -@check_figures_equal(extensions=["png"]) +@check_figures_equal() def test_set_ticks_with_labels(fig_test, fig_ref): """ Test that these two are identical:: @@ -6795,7 +7389,7 @@ def test_loglog(): @image_comparison(["test_loglog_nonpos.png"], remove_text=True, style='mpl20', - tol=0.029 if platform.machine() == 'arm64' else 0) + tol=0.029) def test_loglog_nonpos(): fig, axs = plt.subplots(3, 3) x = np.arange(1, 11) @@ -6815,6 +7409,7 @@ def test_loglog_nonpos(): ax.set_xscale("log", nonpositive=mcx) if mcy: ax.set_yscale("log", nonpositive=mcy) + ax.set_yticks([1e3, 1e7]) # Backcompat tick selection. @mpl.style.context('default') @@ -6883,7 +7478,7 @@ def shared_axes_generator(request): ax = ax_lst[0][0] elif request.param == 'add_axes': fig = plt.figure() - ax = fig.add_axes([.1, .1, .8, .8]) + ax = fig.add_axes((.1, .1, .8, .8)) return fig, ax @@ -6944,8 +7539,8 @@ def test_auto_numticks_log(): fig, ax = plt.subplots() mpl.rcParams['axes.autolimit_mode'] = 'round_numbers' ax.loglog([1e-20, 1e5], [1e-16, 10]) - assert (np.log10(ax.get_xticks()) == np.arange(-26, 18, 4)).all() - assert (np.log10(ax.get_yticks()) == np.arange(-20, 10, 3)).all() + assert_array_equal(np.log10(ax.get_xticks()), np.arange(-26, 11, 4)) + assert_array_equal(np.log10(ax.get_yticks()), np.arange(-20, 5, 3)) def test_broken_barh_empty(): @@ -6962,6 +7557,21 @@ def test_broken_barh_timedelta(): assert pp.get_paths()[0].vertices[2, 0] == mdates.date2num(d0) + 1 / 24 +def test_broken_barh_align(): + fig, ax = plt.subplots() + pc = ax.broken_barh([(0, 10)], (0, 2)) + for path in pc.get_paths(): + assert_array_equal(path.get_extents().intervaly, [0, 2]) + + pc = ax.broken_barh([(0, 10)], (10, 2), align="center") + for path in pc.get_paths(): + assert_array_equal(path.get_extents().intervaly, [9, 11]) + + pc = ax.broken_barh([(0, 10)], (20, 2), align="top") + for path in pc.get_paths(): + assert_array_equal(path.get_extents().intervaly, [18, 20]) + + def test_pandas_pcolormesh(pd): time = pd.date_range('2000-01-01', periods=10) depth = np.arange(20) @@ -7111,63 +7721,6 @@ def test_bar_uint8(): assert patch.xy[0] == x -@image_comparison(['date_timezone_x.png'], tol=1.0) -def test_date_timezone_x(): - # Tests issue 5575 - time_index = [datetime.datetime(2016, 2, 22, hour=x, - tzinfo=dateutil.tz.gettz('Canada/Eastern')) - for x in range(3)] - - # Same Timezone - plt.figure(figsize=(20, 12)) - plt.subplot(2, 1, 1) - with pytest.warns(mpl.MatplotlibDeprecationWarning): - plt.plot_date(time_index, [3] * 3, tz='Canada/Eastern') - - # Different Timezone - plt.subplot(2, 1, 2) - with pytest.warns(mpl.MatplotlibDeprecationWarning): - plt.plot_date(time_index, [3] * 3, tz='UTC') - - -@image_comparison(['date_timezone_y.png']) -def test_date_timezone_y(): - # Tests issue 5575 - time_index = [datetime.datetime(2016, 2, 22, hour=x, - tzinfo=dateutil.tz.gettz('Canada/Eastern')) - for x in range(3)] - - # Same Timezone - plt.figure(figsize=(20, 12)) - plt.subplot(2, 1, 1) - with pytest.warns(mpl.MatplotlibDeprecationWarning): - plt.plot_date([3] * 3, time_index, tz='Canada/Eastern', xdate=False, ydate=True) - - # Different Timezone - plt.subplot(2, 1, 2) - with pytest.warns(mpl.MatplotlibDeprecationWarning): - plt.plot_date([3] * 3, time_index, tz='UTC', xdate=False, ydate=True) - - -@image_comparison(['date_timezone_x_and_y.png'], tol=1.0) -def test_date_timezone_x_and_y(): - # Tests issue 5575 - UTC = datetime.timezone.utc - time_index = [datetime.datetime(2016, 2, 22, hour=x, tzinfo=UTC) - for x in range(3)] - - # Same Timezone - plt.figure(figsize=(20, 12)) - plt.subplot(2, 1, 1) - with pytest.warns(mpl.MatplotlibDeprecationWarning): - plt.plot_date(time_index, time_index, tz='UTC', ydate=True) - - # Different Timezone - plt.subplot(2, 1, 2) - with pytest.warns(mpl.MatplotlibDeprecationWarning): - plt.plot_date(time_index, time_index, tz='US/Eastern', ydate=True) - - @image_comparison(['axisbelow.png'], remove_text=True) def test_axisbelow(): # Test 'line' setting added in 6287. @@ -7274,7 +7827,7 @@ def test_title_no_move_off_page(): # make sure that the automatic title repositioning does not get done. mpl.rcParams['axes.titley'] = None fig = plt.figure() - ax = fig.add_axes([0.1, -0.5, 0.8, 0.2]) + ax = fig.add_axes((0.1, -0.5, 0.8, 0.2)) ax.tick_params(axis="x", bottom=True, top=True, labelbottom=True, labeltop=True) tt = ax.set_title('Boo') @@ -7351,15 +7904,19 @@ def test_tick_param_label_rotation(): ax.yaxis.set_tick_params(which='both', rotation=90) for text in ax.get_xticklabels(which='both'): assert text.get_rotation() == 75 + assert text.get_rotation_mode() == 'default' for text in ax.get_yticklabels(which='both'): assert text.get_rotation() == 90 + assert text.get_rotation_mode() == 'default' - ax2.tick_params(axis='x', labelrotation=53) - ax2.tick_params(axis='y', rotation=35) + ax2.tick_params(axis='x', labelrotation=53, labelrotation_mode='xtick') + ax2.tick_params(axis='y', rotation=35, rotation_mode='ytick') for text in ax2.get_xticklabels(which='major'): assert text.get_rotation() == 53 + assert text.get_rotation_mode() == 'xtick' for text in ax2.get_yticklabels(which='major'): assert text.get_rotation() == 35 + assert text.get_rotation_mode() == 'ytick' @mpl.style.context('default') @@ -7495,9 +8052,42 @@ def test_twinx_knows_limits(): assert_array_equal(xtwin.viewLim.intervalx, ax2.viewLim.intervalx) -def test_zero_linewidth(): - # Check that setting a zero linewidth doesn't error - plt.plot([0, 1], [0, 1], ls='--', lw=0) +class SubclassAxes(Axes): + def __init__(self, *args, foo, **kwargs): + super().__init__(*args, **kwargs) + self.foo = foo + + +def test_twinning_with_axes_class(): + """Check that twinx/y(axes_class=...) gives the appropriate class.""" + _, ax = plt.subplots() + twinx = ax.twinx(axes_class=SubclassAxes, foo=1) + assert isinstance(twinx, SubclassAxes) + assert twinx.foo == 1 + twiny = ax.twiny(axes_class=SubclassAxes, foo=2) + assert isinstance(twiny, SubclassAxes) + assert twiny.foo == 2 + + +def test_twinning_default_axes_class(): + """ + Check that the default class for twinx/y() is Axes, + even if the original is an Axes subclass. + """ + _, ax = plt.subplots(subplot_kw=dict(axes_class=SubclassAxes, foo=1)) + twinx = ax.twinx() + assert type(twinx) is Axes + twiny = ax.twiny() + assert type(twiny) is Axes + + +@mpl.style.context('mpl20') +@check_figures_equal() +def test_stairs_fill_zero_linewidth(fig_test, fig_ref): + fig_test.subplots().stairs( + [1, 2, 3, 4], [1, 2, 3, 4, 5], fill=True, ls='--') + fig_ref.subplots().stairs( + [1, 2, 3, 4], [1, 2, 3, 4, 5], fill=True, ls='-') def test_empty_errorbar_legend(): @@ -7507,7 +8097,7 @@ def test_empty_errorbar_legend(): ax.legend() -@check_figures_equal(extensions=["png"]) +@check_figures_equal() def test_plot_decimal(fig_test, fig_ref): x0 = np.arange(-10, 10, 0.3) y0 = [5.2 * x ** 3 - 2.1 * x ** 2 + 7.34 * x + 4.5 for x in x0] @@ -7519,8 +8109,7 @@ def test_plot_decimal(fig_test, fig_ref): fig_ref.subplots().plot(x0, y0) -# pdf and svg tests fail using travis' old versions of gs and inkscape. -@check_figures_equal(extensions=["png"]) +@check_figures_equal() def test_markerfacecolor_none_alpha(fig_test, fig_ref): fig_test.subplots().plot(0, "o", mfc="none", alpha=.5) fig_ref.subplots().plot(0, "o", mfc="w", alpha=.5) @@ -7584,8 +8173,8 @@ def test_zoom_inset(): axin1 = ax.inset_axes([0.7, 0.7, 0.35, 0.35]) # redraw the data in the inset axes... axin1.pcolormesh(x, y, z[:-1, :-1]) - axin1.set_xlim([1.5, 2.15]) - axin1.set_ylim([2, 2.5]) + axin1.set_xlim(1.5, 2.15) + axin1.set_ylim(2, 2.5) axin1.set_aspect(ax.get_aspect()) with pytest.warns(mpl.MatplotlibDeprecationWarning): @@ -7735,7 +8324,7 @@ def test_scatter_empty_data(): @image_comparison(['annotate_across_transforms.png'], style='mpl20', remove_text=True, - tol=0.025 if platform.machine() == 'arm64' else 0) + tol=0 if platform.machine() == 'x86_64' else 0.025) def test_annotate_across_transforms(): x = np.linspace(0, 10, 200) y = np.exp(-x) * np.sin(x) @@ -7766,7 +8355,7 @@ def inverted(self): @image_comparison(['secondary_xy.png'], style='mpl20', - tol=0.027 if platform.machine() == 'arm64' else 0) + tol=0 if platform.machine() == 'x86_64' else 0.027) def test_secondary_xy(): fig, axs = plt.subplots(1, 2, figsize=(10, 5), constrained_layout=True) @@ -7850,6 +8439,18 @@ def test_secondary_formatter(): secax.xaxis.get_major_formatter(), mticker.ScalarFormatter) +def test_secondary_init_xticks(): + fig, ax = plt.subplots() + secax = ax.secondary_xaxis(1, xticks=[0, 1]) + assert isinstance(secax.xaxis.get_major_locator(), mticker.FixedLocator) + with pytest.raises(TypeError): + secax.set_yticks([0, 1]) + secax = ax.secondary_yaxis(1, yticks=[0, 1]) + assert isinstance(secax.yaxis.get_major_locator(), mticker.FixedLocator) + with pytest.raises(TypeError): + secax.set_xticks([0, 1]) + + def test_secondary_repr(): fig, ax = plt.subplots() secax = ax.secondary_xaxis("top") @@ -7880,10 +8481,9 @@ def color_boxes(fig, ax): """ fig.canvas.draw() - renderer = fig.canvas.get_renderer() bbaxis = [] for nn, axx in enumerate([ax.xaxis, ax.yaxis]): - bb = axx.get_tightbbox(renderer) + bb = axx.get_tightbbox() if bb: axisr = mpatches.Rectangle( (bb.x0, bb.y0), width=bb.width, height=bb.height, @@ -7894,7 +8494,7 @@ def color_boxes(fig, ax): bbspines = [] for nn, a in enumerate(['bottom', 'top', 'left', 'right']): - bb = ax.spines[a].get_window_extent(renderer) + bb = ax.spines[a].get_window_extent() spiner = mpatches.Rectangle( (bb.x0, bb.y0), width=bb.width, height=bb.height, linewidth=0.7, edgecolor="green", facecolor="none", transform=None, @@ -7910,7 +8510,7 @@ def color_boxes(fig, ax): fig.add_artist(rect2) bbax = bb - bb2 = ax.get_tightbbox(renderer) + bb2 = ax.get_tightbbox() rect2 = mpatches.Rectangle( (bb2.x0, bb2.y0), width=bb2.width, height=bb2.height, linewidth=3, edgecolor="red", facecolor="none", transform=None, @@ -7934,7 +8534,7 @@ def test_normal_axes(): ] for nn, b in enumerate(bbaxis): targetbb = mtransforms.Bbox.from_bounds(*target[nn]) - assert_array_almost_equal(b.bounds, targetbb.bounds, decimal=2) + assert_array_almost_equal(b.bounds, targetbb.bounds, decimal=1) target = [ [150.0, 119.999, 930.0, 11.111], @@ -7952,7 +8552,7 @@ def test_normal_axes(): target = [85.5138, 75.88888, 1021.11, 1017.11] targetbb = mtransforms.Bbox.from_bounds(*target) - assert_array_almost_equal(bbtb.bounds, targetbb.bounds, decimal=2) + assert_array_almost_equal(bbtb.bounds, targetbb.bounds, decimal=1) # test that get_position roundtrips to get_window_extent axbb = ax.get_position().transformed(fig.transFigure).bounds @@ -8069,7 +8669,7 @@ def test_minor_accountedfor(): bbspines[n * 2].bounds, targetbb.bounds, atol=1e-2) -@check_figures_equal(extensions=["png"]) +@check_figures_equal() def test_axis_bool_arguments(fig_test, fig_ref): # Test if False and "off" give the same fig_test.add_subplot(211).axis(False) @@ -8181,7 +8781,7 @@ def test_aspect_nonlinear_adjustable_box(): def test_aspect_nonlinear_adjustable_datalim(): fig = plt.figure(figsize=(10, 10)) # Square. - ax = fig.add_axes([.1, .1, .8, .8]) # Square. + ax = fig.add_axes((.1, .1, .8, .8)) # Square. ax.plot([.4, .6], [.4, .6]) # Set minpos to keep logit happy. ax.set(xscale="log", xlim=(1, 100), yscale="logit", ylim=(1 / 101, 1 / 11), @@ -8321,7 +8921,7 @@ def test_unautoscale(axis, auto): assert_array_equal(get_lim(), (-0.5, 0.5)) -@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 @@ -8405,7 +9005,7 @@ def test_multiplot_autoscale(): def test_sharing_does_not_link_positions(): fig = plt.figure() ax0 = fig.add_subplot(221) - ax1 = fig.add_axes([.6, .6, .3, .3], sharex=ax0) + ax1 = fig.add_axes((.6, .6, .3, .3), sharex=ax0) init_pos = ax1.get_position() fig.subplots_adjust(left=0) assert (ax1.get_position().get_points() == init_pos.get_points()).all() @@ -8431,7 +9031,7 @@ def test_2dcolor_plot(fig_test, fig_ref): axs[4].bar(np.arange(10), np.arange(10), color=color.reshape((1, -1))) -@check_figures_equal(extensions=['png']) +@check_figures_equal() def test_shared_axes_clear(fig_test, fig_ref): x = np.arange(0.0, 2*np.pi, 0.01) y = np.sin(x) @@ -8647,6 +9247,23 @@ def test_bar_label_nan_ydata_inverted(): assert labels[0].get_verticalalignment() == 'bottom' +def test_bar_label_padding(): + """Test that bar_label accepts both float and array-like padding.""" + ax = plt.gca() + xs, heights = [1, 2], [3, 4] + rects = ax.bar(xs, heights) + labels1 = ax.bar_label(rects, padding=5) # test float value + assert labels1[0].xyann[1] == 5 + assert labels1[1].xyann[1] == 5 + + labels2 = ax.bar_label(rects, padding=[2, 8]) # test array-like values + assert labels2[0].xyann[1] == 2 + assert labels2[1].xyann[1] == 8 + + with pytest.raises(ValueError, match="padding must be of length"): + ax.bar_label(rects, padding=[1, 2, 3]) + + def test_nan_barlabels(): fig, ax = plt.subplots() bars = ax.bar([1, 2, 3], [np.nan, 1, 2], yerr=[0.2, 0.4, 0.6]) @@ -8678,7 +9295,7 @@ def test_patch_bounds(): # PR 19078 @mpl.style.context('default') def test_warn_ignored_scatter_kwargs(): with pytest.warns(UserWarning, - match=r"You passed a edgecolor/edgecolors"): + match=r"You passed an edgecolor/edgecolors"): plt.scatter([0], [0], marker="+", s=500, facecolor="r", edgecolor="b") @@ -8938,7 +9555,7 @@ def test_bar_leading_nan(): assert np.isfinite(b.get_width()) -@check_figures_equal(extensions=["png"]) +@check_figures_equal() def test_bar_all_nan(fig_test, fig_ref): mpl.style.use("mpl20") ax_test = fig_test.subplots() @@ -9009,8 +9626,8 @@ def test_child_axes_removal(): def test_scatter_color_repr_error(): - def get_next_color(): - return 'blue' # pragma: no cover + def get_next_color(): # pragma: no cover + return 'blue' # currently unused msg = ( r"'c' argument must be a color, a sequence of colors" r", or a sequence of numbers, not 'red\\n'" @@ -9030,7 +9647,7 @@ def test_zorder_and_explicit_rasterization(): @image_comparison(["preset_clip_paths.png"], remove_text=True, style="mpl20", - tol=0.027 if platform.machine() == "arm64" else 0) + tol=0 if platform.machine() == 'x86_64' else 0.027) def test_preset_clip_paths(): fig, ax = plt.subplots() @@ -9079,7 +9696,7 @@ def test_rc_axes_label_formatting(): assert ax.xaxis.label.get_fontweight() == 'bold' -@check_figures_equal(extensions=["png"]) +@check_figures_equal() def test_ecdf(fig_test, fig_ref): data = np.array([0, -np.inf, -np.inf, np.inf, 1, 1, 2]) weights = range(len(data)) @@ -9170,7 +9787,7 @@ def test_axhvlinespan_interpolation(): ax.axhspan(.6, .7, .8, .9, fc="C2", alpha=.5) -@check_figures_equal(extensions=["png"]) +@check_figures_equal() @pytest.mark.parametrize("which", ("x", "y")) def test_axes_clear_behavior(fig_ref, fig_test, which): """Test that the given tick params are not reset by ax.clear().""" @@ -9287,7 +9904,7 @@ def test_latex_pie_percent(fig_test, fig_ref): ax1.pie(data, autopct=r"%1.0f\%%", textprops={'usetex': True}) -@check_figures_equal(extensions=['png']) +@check_figures_equal() def test_violinplot_orientation(fig_test, fig_ref): # Test the `orientation : {'vertical', 'horizontal'}` # parameter and deprecation of `vert: bool`. @@ -9314,17 +9931,17 @@ def test_violinplot_orientation(fig_test, fig_ref): # Deprecation of `vert: bool` keyword with pytest.warns(mpl.MatplotlibDeprecationWarning, - match='vert: bool was deprecated in Matplotlib 3.10'): + match='vert: bool was deprecated in Matplotlib 3.11'): # Compare images between a figure that # uses vert and one that uses orientation. ax_ref = fig_ref.subplots() ax_ref.violinplot(all_data, vert=False) - ax_test = fig_test.subplots() - ax_test.violinplot(all_data, orientation='horizontal') + ax_test = fig_test.subplots() + ax_test.violinplot(all_data, orientation='horizontal') -@check_figures_equal(extensions=['png']) +@check_figures_equal() def test_boxplot_orientation(fig_test, fig_ref): # Test the `orientation : {'vertical', 'horizontal'}` # parameter and deprecation of `vert: bool`. @@ -9364,7 +9981,7 @@ def test_boxplot_orientation(fig_test, fig_ref): @image_comparison(["use_colorizer_keyword.png"], - tol=0.05 if platform.machine() == 'arm64' else 0) + tol=0 if platform.machine() == 'x86_64' else 0.05) def test_use_colorizer_keyword(): # test using the colorizer keyword np.random.seed(0) @@ -9452,3 +10069,127 @@ def test_wrong_use_colorizer(): for kwrd in kwrds: with pytest.raises(ValueError, match=match_str): fig.figimage(c, colorizer=cl, **kwrd) + + +def test_bar_color_precedence(): + # Test the precedence of 'color' and 'facecolor' in bar plots + fig, ax = plt.subplots() + + # case 1: no color specified + bars = ax.bar([1, 2, 3], [4, 5, 6]) + for bar in bars: + assert mcolors.same_color(bar.get_facecolor(), 'blue') + + # case 2: Only 'color' + bars = ax.bar([11, 12, 13], [4, 5, 6], color='red') + for bar in bars: + assert mcolors.same_color(bar.get_facecolor(), 'red') + + # case 3: Only 'facecolor' + bars = ax.bar([21, 22, 23], [4, 5, 6], facecolor='yellow') + for bar in bars: + assert mcolors.same_color(bar.get_facecolor(), 'yellow') + + # case 4: 'facecolor' and 'color' + bars = ax.bar([31, 32, 33], [4, 5, 6], color='red', facecolor='green') + for bar in bars: + assert mcolors.same_color(bar.get_facecolor(), 'green') + + +@check_figures_equal() +def test_axes_set_position_external_bbox_unchanged(fig_test, fig_ref): + # From #29410: Modifying Axes' position also alters the original Bbox + # object used for initialization + bbox = mtransforms.Bbox([[0.0, 0.0], [1.0, 1.0]]) + ax_test = fig_test.add_axes(bbox) + ax_test.set_position([0.25, 0.25, 0.5, 0.5]) + assert (bbox.x0, bbox.y0, bbox.width, bbox.height) == (0.0, 0.0, 1.0, 1.0) + ax_ref = fig_ref.add_axes((0.25, 0.25, 0.5, 0.5)) + + +def test_bar_shape_mismatch(): + x = ["foo", "bar"] + height = [1, 2, 3] + error_message = ( + r"Mismatch is between 'x' with shape \(2,\) and 'height' with shape \(3,\)" + ) + with pytest.raises(ValueError, match=error_message): + plt.bar(x, height) + + +def test_caps_color(): + + # Creates a simple plot with error bars and a specified ecolor + x = np.linspace(0, 10, 10) + mpl.rcParams['lines.markeredgecolor'] = 'green' + ecolor = 'red' + + fig, ax = plt.subplots() + errorbars = ax.errorbar(x, np.sin(x), yerr=0.1, ecolor=ecolor) + + # Tests if the caps have the specified color + for cap in errorbars[2]: + assert mcolors.same_color(cap.get_edgecolor(), ecolor) + + +def test_caps_no_ecolor(): + + # Creates a simple plot with error bars without specifying ecolor + x = np.linspace(0, 10, 10) + mpl.rcParams['lines.markeredgecolor'] = 'green' + fig, ax = plt.subplots() + errorbars = ax.errorbar(x, np.sin(x), yerr=0.1) + + # Tests if the caps have the default color (blue) + for cap in errorbars[2]: + assert mcolors.same_color(cap.get_edgecolor(), "blue") + + +def test_pie_non_finite_values(): + fig, ax = plt.subplots() + df = [5, float('nan'), float('inf')] + + with pytest.raises(ValueError, match='Wedge sizes must be finite numbers'): + ax.pie(df, labels=['A', 'B', 'C']) + + +def test_pie_all_zeros(): + fig, ax = plt.subplots() + with pytest.raises(ValueError, match="All wedge sizes are zero"): + ax.pie([0, 0], labels=["A", "B"]) + + +def test_animated_artists_not_drawn_by_default(): + fig, (ax1, ax2) = plt.subplots(ncols=2) + + imdata = np.random.random((20, 20)) + lndata = imdata[0] + + im = ax1.imshow(imdata, animated=True) + (ln,) = ax2.plot(lndata, animated=True) + + with (unittest.mock.patch.object(im, "draw", name="im.draw") as mocked_im_draw, + unittest.mock.patch.object(ln, "draw", name="ln.draw") as mocked_ln_draw): + fig.draw_without_rendering() + + mocked_im_draw.assert_not_called() + mocked_ln_draw.assert_not_called() + + +def test_errorbar_uses_rcparams(): + with mpl.rc_context({ + "errorbar.capsize": 5.0, + "errorbar.capthick": 2.5, + "errorbar.elinewidth": 1.75, + }): + fig, ax = plt.subplots() + eb = ax.errorbar([0, 1, 2], [1, 2, 3], yerr=[0.1, 0.2, 0.3], fmt="none") + + data_line, caplines, barlinecols = eb.lines + assert data_line is None + assert caplines + + assert_allclose([cap.get_markersize() for cap in caplines], 10.0) + assert_allclose([cap.get_markeredgewidth() for cap in caplines], 2.5) + for barcol in barlinecols: + assert_allclose(barcol.get_linewidths(), 1.75) diff --git a/lib/matplotlib/tests/test_axis.py b/lib/matplotlib/tests/test_axis.py index 33af30662a33..3776b6f054b9 100644 --- a/lib/matplotlib/tests/test_axis.py +++ b/lib/matplotlib/tests/test_axis.py @@ -2,6 +2,7 @@ import matplotlib.pyplot as plt from matplotlib.axis import XTick +from matplotlib.testing.decorators import check_figures_equal def test_tick_labelcolor_array(): @@ -15,9 +16,9 @@ def test_axis_not_in_layout(): fig2, (ax2_left, ax2_right) = plt.subplots(ncols=2, layout='constrained') # 100 label overlapping the end of the axis - ax1_left.set_xlim([0, 100]) + ax1_left.set_xlim(0, 100) # 100 label not overlapping the end of the axis - ax2_left.set_xlim([0, 120]) + ax2_left.set_xlim(0, 120) for ax in ax1_left, ax2_left: ax.set_xticks([0, 100]) @@ -29,3 +30,101 @@ def test_axis_not_in_layout(): # Positions should not be affected by overlapping 100 label assert ax1_left.get_position().bounds == ax2_left.get_position().bounds assert ax1_right.get_position().bounds == ax2_right.get_position().bounds + + +@check_figures_equal() +def test_tick_not_in_layout(fig_test, fig_ref): + # Check that the "very long" ticklabel is ignored from layouting after + # set_in_layout(False); i.e. the layout is as if the ticklabel was empty. + # Ticklabels are set to white so that the actual string doesn't matter. + fig_test.set_layout_engine("constrained") + ax = fig_test.add_subplot(xticks=[0, 1], xticklabels=["short", "very long"]) + ax.tick_params(labelcolor="w") + fig_test.draw_without_rendering() # Ensure ticks are correct. + ax.xaxis.majorTicks[-1].label1.set_in_layout(False) + fig_ref.set_layout_engine("constrained") + ax = fig_ref.add_subplot(xticks=[0, 1], xticklabels=["short", ""]) + ax.tick_params(labelcolor="w") + + +def test_translate_tick_params_reverse(): + fig, ax = plt.subplots() + kw = {'label1On': 'a', 'label2On': 'b', 'tick1On': 'c', 'tick2On': 'd'} + assert (ax.xaxis._translate_tick_params(kw, reverse=True) == + {'labelbottom': 'a', 'labeltop': 'b', 'bottom': 'c', 'top': 'd'}) + assert (ax.yaxis._translate_tick_params(kw, reverse=True) == + {'labelleft': 'a', 'labelright': 'b', 'left': 'c', 'right': 'd'}) + + +def test_get_tick_position_rcParams(): + """Test that get_tick_position() correctly picks up rcParams tick positions.""" + plt.rcParams.update({ + "xtick.top": 1, "xtick.labeltop": 1, "xtick.bottom": 0, "xtick.labelbottom": 0, + "ytick.right": 1, "ytick.labelright": 1, "ytick.left": 0, "ytick.labelleft": 0, + }) + ax = plt.figure().add_subplot() + assert ax.xaxis.get_ticks_position() == "top" + assert ax.yaxis.get_ticks_position() == "right" + + +def test_get_tick_position_tick_top_tick_right(): + """Test that get_tick_position() correctly picks up tick_top() / tick_right().""" + ax = plt.figure().add_subplot() + ax.xaxis.tick_top() + ax.yaxis.tick_right() + assert ax.xaxis.get_ticks_position() == "top" + assert ax.yaxis.get_ticks_position() == "right" + + +def test_get_tick_position_tick_params(): + """Test that get_tick_position() correctly picks up tick_params().""" + ax = plt.figure().add_subplot() + ax.tick_params(top=True, labeltop=True, bottom=False, labelbottom=False, + right=True, labelright=True, left=False, labelleft=False) + assert ax.xaxis.get_ticks_position() == "top" + assert ax.yaxis.get_ticks_position() == "right" + + +def test_grid_rcparams(): + """Tests that `grid.major/minor.*` overwrites `grid.*` in rcParams.""" + plt.rcParams.update({ + "axes.grid": True, "axes.grid.which": "both", + "ytick.minor.visible": True, "xtick.minor.visible": True, + }) + def_linewidth = plt.rcParams["grid.linewidth"] + def_linestyle = plt.rcParams["grid.linestyle"] + def_alpha = plt.rcParams["grid.alpha"] + + plt.rcParams.update({ + "grid.color": "gray", "grid.minor.color": "red", + "grid.major.linestyle": ":", "grid.major.linewidth": 2, + "grid.minor.alpha": 0.6, + }) + _, ax = plt.subplots() + ax.plot([0, 1]) + + assert ax.xaxis.get_major_ticks()[0].gridline.get_color() == "gray" + assert ax.xaxis.get_minor_ticks()[0].gridline.get_color() == "red" + assert ax.xaxis.get_major_ticks()[0].gridline.get_linewidth() == 2 + assert ax.xaxis.get_minor_ticks()[0].gridline.get_linewidth() == def_linewidth + assert ax.xaxis.get_major_ticks()[0].gridline.get_linestyle() == ":" + assert ax.xaxis.get_minor_ticks()[0].gridline.get_linestyle() == def_linestyle + assert ax.xaxis.get_major_ticks()[0].gridline.get_alpha() == def_alpha + assert ax.xaxis.get_minor_ticks()[0].gridline.get_alpha() == 0.6 + + +def test_set_ticks_emits_lim_changed(): + fig, ax1 = plt.subplots() + ax1.set_xlim(0.5, 1) + called_cartesian = [] + ax1.callbacks.connect("xlim_changed", called_cartesian.append) + ax1.set_xticks([0, 100]) + assert called_cartesian + + fig = plt.figure() + ax2 = fig.add_subplot(projection="polar") + ax2.set_ylim(0.5, 1) + called_polar = [] + ax2.callbacks.connect("ylim_changed", called_polar.append) + ax2.set_rticks([1, 2, 3]) + assert called_polar diff --git a/lib/matplotlib/tests/test_backend_bases.py b/lib/matplotlib/tests/test_backend_bases.py index 3e1f524ed1c9..0205eac42fb3 100644 --- a/lib/matplotlib/tests/test_backend_bases.py +++ b/lib/matplotlib/tests/test_backend_bases.py @@ -38,7 +38,7 @@ def check(master_transform, paths, all_transforms, gc, range(len(raw_paths)), offsets, transforms.AffineDeltaTransform(master_transform), facecolors, edgecolors, [], [], [False], - [], 'screen')] + [], 'screen', hatchcolors=[])] uses = rb._iter_collection_uses_per_path( paths, all_transforms, offsets, facecolors, edgecolors) if raw_paths: @@ -64,7 +64,10 @@ def test_canvas_ctor(): def test_get_default_filename(): - assert plt.figure().canvas.get_default_filename() == 'image.png' + fig = plt.figure() + assert fig.canvas.get_default_filename() == "Figure_1.png" + fig.canvas.manager.set_window_title("0:1/2<3") + assert fig.canvas.get_default_filename() == "0_1_2_3.png" def test_canvas_change(): @@ -260,6 +263,8 @@ def test_interactive_colorbar(plot_func, orientation, tool, button, expected): # Set up the mouse movements start_event = MouseEvent( "button_press_event", fig.canvas, *s0, button) + drag_event = MouseEvent( + "motion_notify_event", fig.canvas, *s1, button, buttons={button}) stop_event = MouseEvent( "button_release_event", fig.canvas, *s1, button) @@ -267,12 +272,12 @@ def test_interactive_colorbar(plot_func, orientation, tool, button, expected): if tool == "zoom": tb.zoom() tb.press_zoom(start_event) - tb.drag_zoom(stop_event) + tb.drag_zoom(drag_event) tb.release_zoom(stop_event) else: tb.pan() tb.press_pan(start_event) - tb.drag_pan(stop_event) + tb.drag_pan(drag_event) tb.release_pan(stop_event) # Should be close, but won't be exact due to screen integer resolution @@ -395,6 +400,9 @@ def test_interactive_pan(key, mouseend, expectedxlim, expectedylim): start_event = MouseEvent( "button_press_event", fig.canvas, *sstart, button=MouseButton.LEFT, key=key) + drag_event = MouseEvent( + "motion_notify_event", fig.canvas, *send, button=MouseButton.LEFT, + buttons={MouseButton.LEFT}, key=key) stop_event = MouseEvent( "button_release_event", fig.canvas, *send, button=MouseButton.LEFT, key=key) @@ -402,7 +410,7 @@ def test_interactive_pan(key, mouseend, expectedxlim, expectedylim): tb = NavigationToolbar2(fig.canvas) tb.pan() tb.press_pan(start_event) - tb.drag_pan(stop_event) + tb.drag_pan(drag_event) tb.release_pan(stop_event) # Should be close, but won't be exact due to screen integer resolution assert tuple(ax.get_xlim()) == pytest.approx(expectedxlim, abs=0.02) @@ -510,6 +518,8 @@ def test_interactive_pan_zoom_events(tool, button, patch_vis, forward_nav, t_s): # Set up the mouse movements start_event = MouseEvent("button_press_event", fig.canvas, *s0, button) + drag_event = MouseEvent( + "motion_notify_event", fig.canvas, *s1, button, buttons={button}) stop_event = MouseEvent("button_release_event", fig.canvas, *s1, button) tb = NavigationToolbar2(fig.canvas) @@ -533,18 +543,7 @@ def test_interactive_pan_zoom_events(tool, button, patch_vis, forward_nav, t_s): ylim_b = init_ylim tb.zoom() - tb.press_zoom(start_event) - tb.drag_zoom(stop_event) - tb.release_zoom(stop_event) - - assert ax_t.get_xlim() == pytest.approx(xlim_t, abs=0.15) - assert ax_t.get_ylim() == pytest.approx(ylim_t, abs=0.15) - assert ax_b.get_xlim() == pytest.approx(xlim_b, abs=0.15) - assert ax_b.get_ylim() == pytest.approx(ylim_b, abs=0.15) - # Check if twin-axes are properly triggered - assert ax_t.get_xlim() == pytest.approx(ax_t_twin.get_xlim(), abs=0.15) - assert ax_b.get_xlim() == pytest.approx(ax_b_twin.get_xlim(), abs=0.15) else: # Evaluate expected limits # (call start_pan to make sure ax._pan_start is set) @@ -569,15 +568,16 @@ def test_interactive_pan_zoom_events(tool, button, patch_vis, forward_nav, t_s): ylim_b = init_ylim tb.pan() - tb.press_pan(start_event) - tb.drag_pan(stop_event) - tb.release_pan(stop_event) - assert ax_t.get_xlim() == pytest.approx(xlim_t, abs=0.15) - assert ax_t.get_ylim() == pytest.approx(ylim_t, abs=0.15) - assert ax_b.get_xlim() == pytest.approx(xlim_b, abs=0.15) - assert ax_b.get_ylim() == pytest.approx(ylim_b, abs=0.15) + start_event._process() + drag_event._process() + stop_event._process() + + assert ax_t.get_xlim() == pytest.approx(xlim_t, abs=0.15) + assert ax_t.get_ylim() == pytest.approx(ylim_t, abs=0.15) + assert ax_b.get_xlim() == pytest.approx(xlim_b, abs=0.15) + assert ax_b.get_ylim() == pytest.approx(ylim_b, abs=0.15) - # Check if twin-axes are properly triggered - assert ax_t.get_xlim() == pytest.approx(ax_t_twin.get_xlim(), abs=0.15) - assert ax_b.get_xlim() == pytest.approx(ax_b_twin.get_xlim(), abs=0.15) + # Check if twin-axes are properly triggered + assert ax_t.get_xlim() == pytest.approx(ax_t_twin.get_xlim(), abs=0.15) + assert ax_b.get_xlim() == pytest.approx(ax_b_twin.get_xlim(), abs=0.15) diff --git a/lib/matplotlib/tests/test_backend_cairo.py b/lib/matplotlib/tests/test_backend_cairo.py index 8cc0b319b770..4eaa8fc1ca3c 100644 --- a/lib/matplotlib/tests/test_backend_cairo.py +++ b/lib/matplotlib/tests/test_backend_cairo.py @@ -2,13 +2,14 @@ import pytest +import matplotlib.pyplot as plt from matplotlib.testing.decorators import check_figures_equal from matplotlib import ( collections as mcollections, patches as mpatches, path as mpath) @pytest.mark.backend('cairo') -@check_figures_equal(extensions=["png"]) +@check_figures_equal() def test_patch_alpha_coloring(fig_test, fig_ref): """ Test checks that the patch and collection are rendered with the specified @@ -46,3 +47,7 @@ def test_patch_alpha_coloring(fig_test, fig_ref): facecolor=(1, 0, 0, 0.5), edgecolor=(0, 0, 1, 0.75)) ax.add_collection(col) + + # Have pyplot manage the figures to ensure the cairo backend is used + plt.figure(fig_ref) + plt.figure(fig_test) diff --git a/lib/matplotlib/tests/test_backend_gtk3.py b/lib/matplotlib/tests/test_backend_gtk3.py index 11a3a10c31cb..a299d21a4b7b 100644 --- a/lib/matplotlib/tests/test_backend_gtk3.py +++ b/lib/matplotlib/tests/test_backend_gtk3.py @@ -1,48 +1,29 @@ +import os from matplotlib import pyplot as plt import pytest +from unittest import mock @pytest.mark.backend("gtk3agg", skip_on_importerror=True) -def test_correct_key(): - pytest.xfail("test_widget_send_event is not triggering key_press_event") +def test_save_figure_return(): + from gi.repository import Gtk + fig, ax = plt.subplots() + ax.imshow([[1]]) + with mock.patch("gi.repository.Gtk.FileFilter") as fileFilter: + filt = fileFilter.return_value + filt.get_name.return_value = "Portable Network Graphics" + with mock.patch("gi.repository.Gtk.FileChooserDialog") as dialogChooser: + dialog = dialogChooser.return_value + dialog.get_filter.return_value = filt + dialog.get_filename.return_value = "foobar.png" + dialog.run.return_value = Gtk.ResponseType.OK + fname = fig.canvas.manager.toolbar.save_figure() + os.remove("foobar.png") + assert fname == "foobar.png" - from gi.repository import Gdk, Gtk # type: ignore[import] - fig = plt.figure() - buf = [] - - def send(event): - for key, mod in [ - (Gdk.KEY_a, Gdk.ModifierType.SHIFT_MASK), - (Gdk.KEY_a, 0), - (Gdk.KEY_a, Gdk.ModifierType.CONTROL_MASK), - (Gdk.KEY_agrave, 0), - (Gdk.KEY_Control_L, Gdk.ModifierType.MOD1_MASK), - (Gdk.KEY_Alt_L, Gdk.ModifierType.CONTROL_MASK), - (Gdk.KEY_agrave, - Gdk.ModifierType.CONTROL_MASK - | Gdk.ModifierType.MOD1_MASK - | Gdk.ModifierType.MOD4_MASK), - (0xfd16, 0), # KEY_3270_Play. - (Gdk.KEY_BackSpace, 0), - (Gdk.KEY_BackSpace, Gdk.ModifierType.CONTROL_MASK), - ]: - # This is not actually really the right API: it depends on the - # actual keymap (e.g. on Azerty, shift+agrave -> 0). - Gtk.test_widget_send_key(fig.canvas, key, mod) - - def receive(event): - buf.append(event.key) - if buf == [ - "A", "a", "ctrl+a", - "\N{LATIN SMALL LETTER A WITH GRAVE}", - "alt+control", "ctrl+alt", - "ctrl+alt+super+\N{LATIN SMALL LETTER A WITH GRAVE}", - # (No entry for KEY_3270_Play.) - "backspace", "ctrl+backspace", - ]: - plt.close(fig) - - fig.canvas.mpl_connect("draw_event", send) - fig.canvas.mpl_connect("key_press_event", receive) - plt.show() + with mock.patch("gi.repository.Gtk.MessageDialog"): + dialog.get_filename.return_value = None + dialog.run.return_value = Gtk.ResponseType.OK + fname = fig.canvas.manager.toolbar.save_figure() + assert fname is None diff --git a/lib/matplotlib/tests/test_backend_inline.py b/lib/matplotlib/tests/test_backend_inline.py index 6f0d67d51756..997e1e7186b1 100644 --- a/lib/matplotlib/tests/test_backend_inline.py +++ b/lib/matplotlib/tests/test_backend_inline.py @@ -13,7 +13,7 @@ def test_ipynb(): - nb_path = Path(__file__).parent / 'test_inline_01.ipynb' + nb_path = Path(__file__).parent / 'data/test_inline_01.ipynb' with TemporaryDirectory() as tmpdir: out_path = Path(tmpdir, "out.ipynb") diff --git a/lib/matplotlib/tests/test_backend_macosx.py b/lib/matplotlib/tests/test_backend_macosx.py index 7431481de8ae..0648e43cde94 100644 --- a/lib/matplotlib/tests/test_backend_macosx.py +++ b/lib/matplotlib/tests/test_backend_macosx.py @@ -1,17 +1,19 @@ import os +import threading +from pathlib import Path import pytest +from unittest import mock import matplotlib as mpl import matplotlib.pyplot as plt -try: - from matplotlib.backends import _macosx -except ImportError: - pytest.skip("These are mac only tests", allow_module_level=True) +from matplotlib.testing import subprocess_run_helper -@pytest.mark.backend('macosx') -def test_cached_renderer(): +_test_timeout = 60 + + +def _test_cached_renderer(): # Make sure that figures have an associated renderer after # a fig.canvas.draw() call fig = plt.figure(1) @@ -23,8 +25,14 @@ def test_cached_renderer(): assert fig.canvas.get_renderer()._renderer is not None -@pytest.mark.backend('macosx') -def test_savefig_rcparam(monkeypatch, tmp_path): +@pytest.mark.backend('macosx', skip_on_importerror=True) +def test_cached_renderer(): + subprocess_run_helper(_test_cached_renderer, timeout=_test_timeout, + extra_env={"MPLBACKEND": "macosx"}) + + +def _test_savefig_rcparam(): + tmp_path = Path(os.environ["TEST_SAVEFIG_PATH"]) def new_choose_save_file(title, directory, filename): # Replacement function instead of opening a GUI window @@ -33,9 +41,10 @@ def new_choose_save_file(title, directory, filename): os.makedirs(f"{directory}/test") return f"{directory}/test/{filename}" - monkeypatch.setattr(_macosx, "choose_save_file", new_choose_save_file) fig = plt.figure() - with mpl.rc_context({"savefig.directory": tmp_path}): + with (mock.patch("matplotlib.backends._macosx.choose_save_file", + new_choose_save_file), + mpl.rc_context({"savefig.directory": tmp_path})): fig.canvas.toolbar.save_figure() # Check the saved location got created save_file = f"{tmp_path}/test/{fig.canvas.get_default_filename()}" @@ -46,7 +55,55 @@ def new_choose_save_file(title, directory, filename): assert mpl.rcParams["savefig.directory"] == f"{tmp_path}/test" -@pytest.mark.backend('macosx') +@pytest.mark.backend('macosx', skip_on_importerror=True) +def test_savefig_rcparam(tmp_path): + subprocess_run_helper( + _test_savefig_rcparam, timeout=_test_timeout, + extra_env={"MPLBACKEND": "macosx", "TEST_SAVEFIG_PATH": tmp_path}) + + +@pytest.mark.backend('macosx', skip_on_importerror=True) def test_ipython(): from matplotlib.testing import ipython_in_subprocess ipython_in_subprocess("osx", {(8, 24): "macosx", (7, 0): "MacOSX"}) + + +def _test_save_figure_return(): + fig, ax = plt.subplots() + ax.imshow([[1]]) + prop = "matplotlib.backends._macosx.choose_save_file" + with mock.patch(prop, return_value="foobar.png"): + fname = fig.canvas.manager.toolbar.save_figure() + os.remove("foobar.png") + assert fname == "foobar.png" + with mock.patch(prop, return_value=None): + fname = fig.canvas.manager.toolbar.save_figure() + assert fname is None + + +@pytest.mark.backend('macosx', skip_on_importerror=True) +def test_save_figure_return(): + subprocess_run_helper(_test_save_figure_return, timeout=_test_timeout, + extra_env={"MPLBACKEND": "macosx"}) + + +def _test_create_figure_on_worker_thread_fails(): + def create_figure(): + warn_msg = "Matplotlib GUI outside of the main thread will likely fail." + err_msg = "Cannot create a GUI FigureManager outside the main thread" + with pytest.warns(UserWarning, match=warn_msg): + with pytest.raises(RuntimeError, match=err_msg): + plt.gcf() + + worker = threading.Thread(target=create_figure) + worker.start() + worker.join() + + +@pytest.mark.backend('macosx', skip_on_importerror=True) +def test_create_figure_on_worker_thread_fails(): + subprocess_run_helper( + _test_create_figure_on_worker_thread_fails, + timeout=_test_timeout, + extra_env={"MPLBACKEND": "macosx"} + ) diff --git a/lib/matplotlib/tests/test_backend_nbagg.py b/lib/matplotlib/tests/test_backend_nbagg.py index 23af88d95086..ccf74df20aab 100644 --- a/lib/matplotlib/tests/test_backend_nbagg.py +++ b/lib/matplotlib/tests/test_backend_nbagg.py @@ -14,7 +14,7 @@ def test_ipynb(): - nb_path = Path(__file__).parent / 'test_nbagg_01.ipynb' + nb_path = Path(__file__).parent / 'data/test_nbagg_01.ipynb' with TemporaryDirectory() as tmpdir: out_path = Path(tmpdir, "out.ipynb") diff --git a/lib/matplotlib/tests/test_backend_pdf.py b/lib/matplotlib/tests/test_backend_pdf.py index c816c4715ae2..0bbb06ab37b1 100644 --- a/lib/matplotlib/tests/test_backend_pdf.py +++ b/lib/matplotlib/tests/test_backend_pdf.py @@ -1,7 +1,6 @@ import datetime import decimal import io -import os from pathlib import Path import numpy as np @@ -13,10 +12,10 @@ ) from matplotlib.cbook import _get_data_path from matplotlib.ft2font import FT2Font -from matplotlib.font_manager import findfont, FontProperties from matplotlib.backends._backend_pdf_ps import get_glyphs_subset, font_as_file from matplotlib.backends.backend_pdf import PdfPages from matplotlib.patches import Rectangle +from matplotlib.testing import _gen_multi_font_text, _has_tex_package from matplotlib.testing.decorators import check_figures_equal, image_comparison from matplotlib.testing._markers import needs_usetex @@ -41,22 +40,6 @@ def test_use14corefonts(): ax.axhline(0.5, linewidth=0.5) -@pytest.mark.parametrize('fontname, fontfile', [ - ('DejaVu Sans', 'DejaVuSans.ttf'), - ('WenQuanYi Zen Hei', 'wqy-zenhei.ttc'), -]) -@pytest.mark.parametrize('fonttype', [3, 42]) -def test_embed_fonts(fontname, fontfile, fonttype): - if Path(findfont(FontProperties(family=[fontname]))).name != fontfile: - pytest.skip(f'Font {fontname!r} may be missing') - - rcParams['pdf.fonttype'] = fonttype - fig, ax = plt.subplots() - ax.plot([1, 2, 3]) - ax.set_title('Axes Title', font=fontname) - fig.savefig(io.BytesIO(), format='pdf') - - def test_multipage_pagecount(): with PdfPages(io.BytesIO()) as pdf: assert pdf.get_pagecount() == 0 @@ -307,19 +290,21 @@ def test_text_urls_tex(): assert annot.Rect[1] == decimal.Decimal('0.7') * 72 -def test_pdfpages_fspath(): - with PdfPages(Path(os.devnull)) as pdf: +def test_pdfpages_fspath(tmp_path): + with PdfPages(tmp_path / 'unused.pdf') as pdf: pdf.savefig(plt.figure()) -@image_comparison(['hatching_legend.pdf']) -def test_hatching_legend(): +@image_comparison(['hatching_legend.pdf'], style='mpl20') +def test_hatching_legend(text_placeholders): """Test for correct hatching on patches in legend""" fig = plt.figure(figsize=(1, 2)) a = Rectangle([0, 0], 0, 0, facecolor="green", hatch="XXXX") b = Rectangle([0, 0], 0, 0, facecolor="blue", hatch="XXXX") + # Verify that hatches in PDFs work after empty labels. See + # https://github.com/matplotlib/matplotlib/issues/4469 fig.legend([a, b, a, b], ["", "", "", ""]) @@ -394,30 +379,26 @@ def test_glyphs_subset(): assert subfont.get_num_glyphs() == nosubfont.get_num_glyphs() -@image_comparison(["multi_font_type3.pdf"], tol=4.6) +@image_comparison(["multi_font_type3.pdf"]) def test_multi_font_type3(): - fp = fm.FontProperties(family=["WenQuanYi Zen Hei"]) - if Path(fm.findfont(fp)).name != "wqy-zenhei.ttc": - pytest.skip("Font may be missing") - - plt.rc('font', family=['DejaVu Sans', 'WenQuanYi Zen Hei'], size=27) + fonts, test_str = _gen_multi_font_text() + plt.rc('font', family=fonts, size=16) plt.rc('pdf', fonttype=3) fig = plt.figure() - fig.text(0.15, 0.475, "There are 几个汉字 in between!") + fig.text(0.5, 0.5, test_str, + horizontalalignment='center', verticalalignment='center') -@image_comparison(["multi_font_type42.pdf"], tol=2.2) +@image_comparison(["multi_font_type42.pdf"]) def test_multi_font_type42(): - fp = fm.FontProperties(family=["WenQuanYi Zen Hei"]) - if Path(fm.findfont(fp)).name != "wqy-zenhei.ttc": - pytest.skip("Font may be missing") - - plt.rc('font', family=['DejaVu Sans', 'WenQuanYi Zen Hei'], size=27) + fonts, test_str = _gen_multi_font_text() + plt.rc('font', family=fonts, size=16) plt.rc('pdf', fonttype=42) fig = plt.figure() - fig.text(0.15, 0.475, "There are 几个汉字 in between!") + fig.text(0.5, 0.5, test_str, + horizontalalignment='center', verticalalignment='center') @pytest.mark.parametrize('family_name, file_name', @@ -443,6 +424,274 @@ def test_truetype_conversion(recwarn): mpl.rcParams['pdf.fonttype'] = 3 fig, ax = plt.subplots() ax.text(0, 0, "ABCDE", - font=Path(__file__).with_name("mpltest.ttf"), fontsize=80) + font=Path(__file__).parent / "data/mpltest.ttf", fontsize=80) + ax.set_xticks([]) + ax.set_yticks([]) + + +@pytest.mark.skipif(not _has_tex_package("heuristica"), + reason="LaTeX lacks heuristica package") +@image_comparison(["font-heuristica.pdf"]) +def test_font_heuristica(): + # Heuristica uses the callothersubr operator for some glyphs + mpl.rcParams['text.latex.preamble'] = '\n'.join(( + r'\usepackage{heuristica}', + r'\usepackage[T1]{fontenc}', + r'\usepackage[utf8]{inputenc}' + )) + fig, ax = plt.subplots() + ax.text(0.1, 0.1, r"BHTem fi ffl 1234", usetex=True, fontsize=50) + ax.set_xticks([]) + ax.set_yticks([]) + + +@pytest.mark.skipif(not _has_tex_package("DejaVuSans"), + reason="LaTeX lacks DejaVuSans package") +@image_comparison(["font-dejavusans.pdf"]) +def test_font_dejavusans(): + # DejaVuSans uses the seac operator to compose characters with diacritics + mpl.rcParams['text.latex.preamble'] = '\n'.join(( + r'\usepackage{DejaVuSans}', + r'\usepackage[T1]{fontenc}', + r'\usepackage[utf8]{inputenc}' + )) + + fig, ax = plt.subplots() + ax.text(0.1, 0.1, r"\textsf{ñäö ABCDabcd}", usetex=True, fontsize=50) + ax.text(0.1, 0.3, r"\textsf{fi ffl 1234}", usetex=True, fontsize=50) + ax.set_xticks([]) + ax.set_yticks([]) + + +@pytest.mark.skipif(not _has_tex_package("charter"), + reason="LaTeX lacks charter package") +@image_comparison(["font-bitstream-charter.pdf"]) +def test_font_bitstream_charter(): + mpl.rcParams['text.latex.preamble'] = '\n'.join(( + r'\usepackage{charter}', + r'\usepackage[T1]{fontenc}', + r'\usepackage[utf8]{inputenc}' + )) + fig, ax = plt.subplots() + ax.text(0.1, 0.1, r"åüš ABCDabcd", usetex=True, fontsize=50) + ax.text(0.1, 0.3, r"fi ffl 1234", usetex=True, fontsize=50) ax.set_xticks([]) ax.set_yticks([]) + + +def test_scatter_offaxis_colored_pdf_size(): + """ + Test that off-axis scatter plots with per-point colors don't bloat PDFs. + + Regression test for issue #2488. When scatter points with per-point colors + are completely outside the visible axes, the PDF backend should skip + writing those markers to significantly reduce file size. + """ + # Use John Hunter's birthday as random seed for reproducibility + rng = np.random.default_rng(19680801) + + n_points = 1000 + x = rng.random(n_points) * 10 + y = rng.random(n_points) * 10 + c = rng.random(n_points) + + # Test 1: Scatter with per-point colors, all points OFF-AXIS + fig1, ax1 = plt.subplots() + ax1.scatter(x, y, c=c) + ax1.set_xlim(20, 30) # Move view completely away from data (x is 0-10) + ax1.set_ylim(20, 30) # Move view completely away from data (y is 0-10) + + buf1 = io.BytesIO() + fig1.savefig(buf1, format='pdf') + size_offaxis_colored = buf1.tell() + plt.close(fig1) + + # Test 2: Empty scatter (baseline - accounts for scatter call overhead) + fig2, ax2 = plt.subplots() + ax2.scatter([], []) # Empty scatter to match the axes structure + ax2.set_xlim(20, 30) + ax2.set_ylim(20, 30) + + buf2 = io.BytesIO() + fig2.savefig(buf2, format='pdf') + size_empty = buf2.tell() + plt.close(fig2) + + # Test 3: Scatter with visible markers (should be much larger) + fig3, ax3 = plt.subplots() + ax3.scatter(x + 20, y + 20, c=c) # Shift points to be visible + ax3.set_xlim(20, 30) + ax3.set_ylim(20, 30) + + buf3 = io.BytesIO() + fig3.savefig(buf3, format='pdf') + size_visible = buf3.tell() + plt.close(fig3) + + # The off-axis colored scatter should be close to empty size. + # Since the axes are identical, the difference should be minimal + # (just the scatter collection setup, no actual marker data). + # Use a tight tolerance since axes output is identical. + assert size_offaxis_colored < size_empty + 5_000, ( + f"Off-axis colored scatter PDF ({size_offaxis_colored} bytes) is too large. " + f"Expected close to empty scatter size ({size_empty} bytes). " + f"Markers may not be properly skipped." + ) + + # The visible scatter should be significantly larger than both empty and + # off-axis, demonstrating the optimization is working. + assert size_visible > size_empty + 15_000, ( + f"Visible scatter PDF ({size_visible} bytes) should be much larger " + f"than empty ({size_empty} bytes) to validate the test." + ) + assert size_visible > size_offaxis_colored + 15_000, ( + f"Visible scatter PDF ({size_visible} bytes) should be much larger " + f"than off-axis ({size_offaxis_colored} bytes) to validate optimization." + ) + + +@check_figures_equal(extensions=["pdf"]) +def test_scatter_offaxis_colored_visual(fig_test, fig_ref): + """ + Test that on-axis scatter with per-point colors still renders correctly. + + Ensures the optimization for off-axis markers doesn't break normal + scatter rendering. + """ + rng = np.random.default_rng(19680801) + + n_points = 100 + x = rng.random(n_points) * 5 + y = rng.random(n_points) * 5 + c = rng.random(n_points) + + # Test figure: scatter with clipping optimization + ax_test = fig_test.subplots() + ax_test.scatter(x, y, c=c, s=50) + ax_test.set_xlim(0, 10) + ax_test.set_ylim(0, 10) + + # Reference figure: should look identical + ax_ref = fig_ref.subplots() + ax_ref.scatter(x, y, c=c, s=50) + ax_ref.set_xlim(0, 10) + ax_ref.set_ylim(0, 10) + + +@check_figures_equal(extensions=["pdf"]) +def test_scatter_mixed_onoff_axis(fig_test, fig_ref): + """ + Test scatter with some points on-axis and some off-axis. + + Ensures the optimization correctly handles the common case where only + some markers are outside the visible area. + """ + rng = np.random.default_rng(19680801) + + # Create points: half on-axis (0-5), half off-axis (15-20) + n_points = 50 + x_on = rng.random(n_points) * 5 + y_on = rng.random(n_points) * 5 + x_off = rng.random(n_points) * 5 + 15 + y_off = rng.random(n_points) * 5 + 15 + + x = np.concatenate([x_on, x_off]) + y = np.concatenate([y_on, y_off]) + c = rng.random(2 * n_points) + + # Test figure: scatter with mixed points + ax_test = fig_test.subplots() + ax_test.scatter(x, y, c=c, s=50) + ax_test.set_xlim(0, 10) + ax_test.set_ylim(0, 10) + + # Reference figure: only the on-axis points should be visible + ax_ref = fig_ref.subplots() + ax_ref.scatter(x_on, y_on, c=c[:n_points], s=50) + ax_ref.set_xlim(0, 10) + ax_ref.set_ylim(0, 10) + + +@check_figures_equal(extensions=["pdf"]) +def test_scatter_large_markers_partial_clip(fig_test, fig_ref): + """ + Test that large markers are rendered when partially visible. + + Addresses reviewer concern: markers with centers outside the canvas but + with edges extending into the visible area should still be rendered. + """ + # Create markers just outside the visible area + # Canvas is 0-10, markers at x=-0.5 and x=10.5 + x = np.array([-0.5, 10.5, 5]) # left edge, right edge, center + y = np.array([5, 5, -0.5]) # center, center, bottom edge + c = np.array([0.2, 0.5, 0.8]) + + # Test figure: large markers (s=500 ≈ 11 points radius) + # Centers are outside, but marker edges extend into visible area + ax_test = fig_test.subplots() + ax_test.scatter(x, y, c=c, s=500) + ax_test.set_xlim(0, 10) + ax_test.set_ylim(0, 10) + + # Reference figure: same plot (should render identically) + ax_ref = fig_ref.subplots() + ax_ref.scatter(x, y, c=c, s=500) + ax_ref.set_xlim(0, 10) + ax_ref.set_ylim(0, 10) + + +@check_figures_equal(extensions=["pdf"]) +def test_scatter_logscale(fig_test, fig_ref): + """ + Test scatter optimization with logarithmic scales. + + Ensures bounds checking works correctly in log-transformed coordinates. + """ + rng = np.random.default_rng(19680801) + + # Create points across several orders of magnitude + n_points = 50 + x = 10 ** (rng.random(n_points) * 4) # 1 to 10000 + y = 10 ** (rng.random(n_points) * 4) + c = rng.random(n_points) + + # Test figure: log scale with points mostly outside view + ax_test = fig_test.subplots() + ax_test.scatter(x, y, c=c, s=50) + ax_test.set_xscale('log') + ax_test.set_yscale('log') + ax_test.set_xlim(100, 1000) # Only show middle range + ax_test.set_ylim(100, 1000) + + # Reference figure: should render identically + ax_ref = fig_ref.subplots() + ax_ref.scatter(x, y, c=c, s=50) + ax_ref.set_xscale('log') + ax_ref.set_yscale('log') + ax_ref.set_xlim(100, 1000) + ax_ref.set_ylim(100, 1000) + + +@check_figures_equal(extensions=["pdf"]) +def test_scatter_polar(fig_test, fig_ref): + """ + Test scatter optimization with polar coordinates. + + Ensures bounds checking works correctly in polar projections. + """ + rng = np.random.default_rng(19680801) + + n_points = 50 + theta = rng.random(n_points) * 2 * np.pi + r = rng.random(n_points) * 3 + c = rng.random(n_points) + + # Test figure: polar projection + ax_test = fig_test.subplots(subplot_kw={'projection': 'polar'}) + ax_test.scatter(theta, r, c=c, s=50) + ax_test.set_ylim(0, 2) # Limit radial range + + # Reference figure: should render identically + ax_ref = fig_ref.subplots(subplot_kw={'projection': 'polar'}) + ax_ref.scatter(theta, r, c=c, s=50) + ax_ref.set_ylim(0, 2) diff --git a/lib/matplotlib/tests/test_backend_ps.py b/lib/matplotlib/tests/test_backend_ps.py index cc968795802e..9859a286e5fd 100644 --- a/lib/matplotlib/tests/test_backend_ps.py +++ b/lib/matplotlib/tests/test_backend_ps.py @@ -1,5 +1,4 @@ from collections import Counter -from pathlib import Path import io import re import tempfile @@ -7,9 +6,10 @@ import numpy as np import pytest -from matplotlib import cbook, path, patheffects, font_manager as fm +from matplotlib import cbook, path, patheffects from matplotlib.figure import Figure from matplotlib.patches import Ellipse +from matplotlib.testing import _gen_multi_font_text from matplotlib.testing._markers import needs_ghostscript, needs_usetex from matplotlib.testing.decorators import check_figures_equal, image_comparison import matplotlib as mpl @@ -318,30 +318,26 @@ def test_no_duplicate_definition(): assert max(Counter(wds).values()) == 1 -@image_comparison(["multi_font_type3.eps"], tol=0.51) +@image_comparison(["multi_font_type3.eps"]) def test_multi_font_type3(): - fp = fm.FontProperties(family=["WenQuanYi Zen Hei"]) - if Path(fm.findfont(fp)).name != "wqy-zenhei.ttc": - pytest.skip("Font may be missing") - - plt.rc('font', family=['DejaVu Sans', 'WenQuanYi Zen Hei'], size=27) + fonts, test_str = _gen_multi_font_text() + plt.rc('font', family=fonts, size=16) plt.rc('ps', fonttype=3) fig = plt.figure() - fig.text(0.15, 0.475, "There are 几个汉字 in between!") + fig.text(0.5, 0.5, test_str, + horizontalalignment='center', verticalalignment='center') -@image_comparison(["multi_font_type42.eps"], tol=1.6) +@image_comparison(["multi_font_type42.eps"]) def test_multi_font_type42(): - fp = fm.FontProperties(family=["WenQuanYi Zen Hei"]) - if Path(fm.findfont(fp)).name != "wqy-zenhei.ttc": - pytest.skip("Font may be missing") - - plt.rc('font', family=['DejaVu Sans', 'WenQuanYi Zen Hei'], size=27) + fonts, test_str = _gen_multi_font_text() + plt.rc('font', family=fonts, size=16) plt.rc('ps', fonttype=42) fig = plt.figure() - fig.text(0.15, 0.475, "There are 几个汉字 in between!") + fig.text(0.5, 0.5, test_str, + horizontalalignment='center', verticalalignment='center') @image_comparison(["scatter.eps"]) @@ -358,7 +354,11 @@ def test_path_collection(): sizes = [0.02, 0.04] pc = mcollections.PathCollection(paths, sizes, zorder=-1, facecolors='yellow', offsets=offsets) - ax.add_collection(pc) + # Note: autolim=False is used to keep the view limits as is for now, + # given the updated behavior of autolim=True to also update the view + # limits. It may be reasonable to test the limits handling in the future + # as well. This will require regenerating the reference image. + ax.add_collection(pc, autolim=False) ax.set_xlim(0, 1) diff --git a/lib/matplotlib/tests/test_backend_qt.py b/lib/matplotlib/tests/test_backend_qt.py index 83d169e5c701..fda0f978ea02 100644 --- a/lib/matplotlib/tests/test_backend_qt.py +++ b/lib/matplotlib/tests/test_backend_qt.py @@ -15,6 +15,7 @@ from matplotlib import _c_internal_utils try: + from matplotlib.backends.qt_compat import QtCore # type: ignore[attr-defined] from matplotlib.backends.qt_compat import QtGui # type: ignore[attr-defined] # noqa: E501, F401 from matplotlib.backends.qt_compat import QtWidgets # type: ignore[attr-defined] from matplotlib.backends.qt_editor import _formlayout @@ -25,12 +26,6 @@ _test_timeout = 60 # A reasonably safe value for slower architectures. -@pytest.fixture -def qt_core(request): - from matplotlib.backends.qt_compat import QtCore - return QtCore - - @pytest.mark.backend('QtAgg', skip_on_importerror=True) def test_fig_close(): @@ -101,7 +96,7 @@ def test_fig_close(): 'QtAgg', marks=pytest.mark.backend('QtAgg', skip_on_importerror=True)), ]) -def test_correct_key(backend, qt_core, qt_key, qt_mods, answer, monkeypatch): +def test_correct_key(backend, qt_key, qt_mods, answer, monkeypatch): """ Make a figure. Send a key_press_event event (using non-public, qtX backend specific api). @@ -154,11 +149,18 @@ def test_device_pixel_ratio_change(): def set_device_pixel_ratio(ratio): p.return_value = ratio - # The value here doesn't matter, as we can't mock the C++ QScreen - # object, but can override the functional wrapper around it. - # Emitting this event is simply to trigger the DPI change handler - # in Matplotlib in the same manner that it would occur normally. - screen.logicalDotsPerInchChanged.emit(96) + window = qt_canvas.window().windowHandle() + current_version = tuple(int(x) for x in QtCore.qVersion().split('.', 2)[:2]) + if current_version >= (6, 6): + QtCore.QCoreApplication.sendEvent( + window, + QtCore.QEvent(QtCore.QEvent.Type.DevicePixelRatioChange)) + else: + # The value here doesn't matter, as we can't mock the C++ QScreen + # object, but can override the functional wrapper around it. + # Emitting this event is simply to trigger the DPI change handler + # in Matplotlib in the same manner that it would occur normally. + window.screen().logicalDotsPerInchChanged.emit(96) qt_canvas.draw() qt_canvas.flush_events() @@ -167,53 +169,43 @@ def set_device_pixel_ratio(ratio): assert qt_canvas.device_pixel_ratio == ratio qt_canvas.manager.show() + qt_canvas.draw() + qt_canvas.flush_events() size = qt_canvas.size() - screen = qt_canvas.window().windowHandle().screen() - set_device_pixel_ratio(3) - - # The DPI and the renderer width/height change - assert fig.dpi == 360 - assert qt_canvas.renderer.width == 1800 - assert qt_canvas.renderer.height == 720 - - # The actual widget size and figure logical size don't change. - assert size.width() == 600 - assert size.height() == 240 - assert qt_canvas.get_width_height() == (600, 240) - assert (fig.get_size_inches() == (5, 2)).all() - - set_device_pixel_ratio(2) - - # The DPI and the renderer width/height change - assert fig.dpi == 240 - assert qt_canvas.renderer.width == 1200 - assert qt_canvas.renderer.height == 480 - # The actual widget size and figure logical size don't change. - assert size.width() == 600 - assert size.height() == 240 - assert qt_canvas.get_width_height() == (600, 240) - assert (fig.get_size_inches() == (5, 2)).all() + options = [ + (None, 360, 1800, 720), # Use ratio at startup time. + (3, 360, 1800, 720), # Change to same ratio. + (2, 240, 1200, 480), # Change to different ratio. + (1.5, 180, 900, 360), # Fractional ratio. + ] + for ratio, dpi, width, height in options: + if ratio is not None: + set_device_pixel_ratio(ratio) - set_device_pixel_ratio(1.5) + # The DPI and the renderer width/height change + assert fig.dpi == dpi + assert qt_canvas.renderer.width == width + assert qt_canvas.renderer.height == height - # The DPI and the renderer width/height change - assert fig.dpi == 180 - assert qt_canvas.renderer.width == 900 - assert qt_canvas.renderer.height == 360 + # The actual widget size and figure logical size don't change. + assert size.width() == 600 + assert size.height() == 240 + assert qt_canvas.get_width_height() == (600, 240) + assert (fig.get_size_inches() == (5, 2)).all() - # The actual widget size and figure logical size don't change. - assert size.width() == 600 - assert size.height() == 240 - assert qt_canvas.get_width_height() == (600, 240) - assert (fig.get_size_inches() == (5, 2)).all() + # check that closing the figure restores the original dpi + plt.close(fig) + assert fig.dpi == 120 @pytest.mark.backend('QtAgg', skip_on_importerror=True) def test_subplottool(): fig, ax = plt.subplots() with mock.patch("matplotlib.backends.qt_compat._exec", lambda obj: None): - fig.canvas.manager.toolbar.configure_subplots() + tool = fig.canvas.manager.toolbar.configure_subplots() + assert tool is not None + assert tool == fig.canvas.manager.toolbar.configure_subplots() @pytest.mark.backend('QtAgg', skip_on_importerror=True) @@ -226,6 +218,21 @@ def test_figureoptions(): fig.canvas.manager.toolbar.edit_parameters() +@pytest.mark.backend('QtAgg', skip_on_importerror=True) +def test_save_figure_return(tmp_path): + fig, ax = plt.subplots() + ax.imshow([[1]]) + expected = tmp_path / "foobar.png" + prop = "matplotlib.backends.qt_compat.QtWidgets.QFileDialog.getSaveFileName" + with mock.patch(prop, return_value=(str(expected), None)): + fname = fig.canvas.manager.toolbar.save_figure() + assert fname == str(expected) + assert expected.exists() + with mock.patch(prop, return_value=(None, None)): + fname = fig.canvas.manager.toolbar.save_figure() + assert fname is None + + @pytest.mark.backend('QtAgg', skip_on_importerror=True) def test_figureoptions_with_datetime_axes(): fig, ax = plt.subplots() @@ -319,7 +326,7 @@ def _get_testable_qt_backends(): @pytest.mark.backend('QtAgg', skip_on_importerror=True) -def test_fig_sigint_override(qt_core): +def test_fig_sigint_override(): from matplotlib.backends.backend_qt5 import _BackendQT5 # Create a figure plt.figure() @@ -334,10 +341,10 @@ def fire_signal_and_quit(): event_loop_handler = signal.getsignal(signal.SIGINT) # Request event loop exit - qt_core.QCoreApplication.exit() + QtCore.QCoreApplication.exit() # Timer to exit event loop - qt_core.QTimer.singleShot(0, fire_signal_and_quit) + QtCore.QTimer.singleShot(0, fire_signal_and_quit) # Save original SIGINT handler original_handler = signal.getsignal(signal.SIGINT) @@ -362,7 +369,7 @@ def custom_handler(signum, frame): # Repeat again to test that SIG_DFL and SIG_IGN will not be overridden for custom_handler in (signal.SIG_DFL, signal.SIG_IGN): - qt_core.QTimer.singleShot(0, fire_signal_and_quit) + QtCore.QTimer.singleShot(0, fire_signal_and_quit) signal.signal(signal.SIGINT, custom_handler) _BackendQT5.mainloop() diff --git a/lib/matplotlib/tests/test_backend_registry.py b/lib/matplotlib/tests/test_backend_registry.py index 80c2ce4fc51a..2bd8e161bd6b 100644 --- a/lib/matplotlib/tests/test_backend_registry.py +++ b/lib/matplotlib/tests/test_backend_registry.py @@ -3,7 +3,6 @@ import pytest -import matplotlib as mpl from matplotlib.backends import BackendFilter, backend_registry @@ -95,16 +94,6 @@ def test_backend_normalization(backend, normalized): assert backend_registry._backend_module_name(backend) == normalized -def test_deprecated_rcsetup_attributes(): - match = "was deprecated in Matplotlib 3.9" - with pytest.warns(mpl.MatplotlibDeprecationWarning, match=match): - mpl.rcsetup.interactive_bk - with pytest.warns(mpl.MatplotlibDeprecationWarning, match=match): - mpl.rcsetup.non_interactive_bk - with pytest.warns(mpl.MatplotlibDeprecationWarning, match=match): - mpl.rcsetup.all_backends - - def test_entry_points_inline(): pytest.importorskip('matplotlib_inline') backends = backend_registry.list_all() diff --git a/lib/matplotlib/tests/test_backend_svg.py b/lib/matplotlib/tests/test_backend_svg.py index b850a9ab6ff5..7864b3bb68bd 100644 --- a/lib/matplotlib/tests/test_backend_svg.py +++ b/lib/matplotlib/tests/test_backend_svg.py @@ -13,6 +13,7 @@ from matplotlib.patches import Circle from matplotlib.text import Text import matplotlib.pyplot as plt +from matplotlib.testing import _gen_multi_font_text from matplotlib.testing.decorators import check_figures_equal, image_comparison from matplotlib.testing._markers import needs_usetex from matplotlib import font_manager as fm @@ -73,7 +74,8 @@ def test_bold_font_output(): ax.plot(np.arange(10), np.arange(10)) ax.set_xlabel('nonbold-xlabel') ax.set_ylabel('bold-ylabel', fontweight='bold') - ax.set_title('bold-title', fontweight='bold') + # set weight as integer to assert it's handled properly + ax.set_title('bold-title', fontweight=600) @image_comparison(['bold_font_output_with_none_fonttype.svg']) @@ -83,10 +85,11 @@ def test_bold_font_output_with_none_fonttype(): ax.plot(np.arange(10), np.arange(10)) ax.set_xlabel('nonbold-xlabel') ax.set_ylabel('bold-ylabel', fontweight='bold') - ax.set_title('bold-title', fontweight='bold') + # set weight as integer to assert it's handled properly + ax.set_title('bold-title', fontweight=600) -@check_figures_equal(tol=20) +@check_figures_equal(extensions=['svg'], tol=20) def test_rasterized(fig_test, fig_ref): t = np.arange(0, 100) * (2.3) x = np.cos(t) @@ -101,7 +104,7 @@ def test_rasterized(fig_test, fig_ref): ax_test.plot(x+1, y, "-", c="b", lw=10, rasterized=True) -@check_figures_equal() +@check_figures_equal(extensions=['svg']) def test_rasterized_ordering(fig_test, fig_ref): t = np.arange(0, 100) * (2.3) x = np.cos(t) @@ -215,7 +218,7 @@ def test_unicode_won(): tree = xml.etree.ElementTree.fromstring(buf) ns = 'http://www.w3.org/2000/svg' - won_id = 'SFSS3583-8e' + won_id = 'SFSS1728-8e' assert len(tree.findall(f'.//{{{ns}}}path[@d][@id="{won_id}"]')) == 1 assert f'#{won_id}' in tree.find(f'.//{{{ns}}}use').attrib.values() @@ -524,30 +527,26 @@ def test_svg_metadata(): assert values == metadata['Keywords'] -@image_comparison(["multi_font_aspath.svg"], tol=1.8) +@image_comparison(["multi_font_aspath.svg"]) def test_multi_font_type3(): - fp = fm.FontProperties(family=["WenQuanYi Zen Hei"]) - if Path(fm.findfont(fp)).name != "wqy-zenhei.ttc": - pytest.skip("Font may be missing") - - plt.rc('font', family=['DejaVu Sans', 'WenQuanYi Zen Hei'], size=27) + fonts, test_str = _gen_multi_font_text() + plt.rc('font', family=fonts, size=16) plt.rc('svg', fonttype='path') fig = plt.figure() - fig.text(0.15, 0.475, "There are 几个汉字 in between!") + fig.text(0.5, 0.5, test_str, + horizontalalignment='center', verticalalignment='center') @image_comparison(["multi_font_astext.svg"]) def test_multi_font_type42(): - fp = fm.FontProperties(family=["WenQuanYi Zen Hei"]) - if Path(fm.findfont(fp)).name != "wqy-zenhei.ttc": - pytest.skip("Font may be missing") - - fig = plt.figure() + fonts, test_str = _gen_multi_font_text() + plt.rc('font', family=fonts, size=16) plt.rc('svg', fonttype='none') - plt.rc('font', family=['DejaVu Sans', 'WenQuanYi Zen Hei'], size=27) - fig.text(0.15, 0.475, "There are 几个汉字 in between!") + fig = plt.figure() + fig.text(0.5, 0.5, test_str, + horizontalalignment='center', verticalalignment='center') @pytest.mark.parametrize('metadata,error,message', [ diff --git a/lib/matplotlib/tests/test_backend_tk.py b/lib/matplotlib/tests/test_backend_tk.py index 89782e8a66f3..32212610ffc1 100644 --- a/lib/matplotlib/tests/test_backend_tk.py +++ b/lib/matplotlib/tests/test_backend_tk.py @@ -4,6 +4,7 @@ import platform import subprocess import sys +from unittest.mock import patch import pytest @@ -168,7 +169,9 @@ def test_never_update(): plt.show(block=False) plt.draw() # Test FigureCanvasTkAgg. - fig.canvas.toolbar.configure_subplots() # Test NavigationToolbar2Tk. + tool = fig.canvas.toolbar.configure_subplots() # Test NavigationToolbar2Tk. + assert tool is not None + assert tool == fig.canvas.toolbar.configure_subplots() # Tool is reused internally. # Test FigureCanvasTk filter_destroy callback fig.canvas.get_tk_widget().after(100, plt.close, fig) @@ -196,6 +199,23 @@ class Toolbar(NavigationToolbar2Tk): print("success") +@_isolated_tk_test(success_count=2) +def test_save_figure_return(): + import matplotlib.pyplot as plt + from unittest import mock + fig = plt.figure() + prop = "tkinter.filedialog.asksaveasfilename" + with mock.patch(prop, return_value="foobar.png"): + fname = fig.canvas.manager.toolbar.save_figure() + os.remove("foobar.png") + assert fname == "foobar.png" + print("success") + with mock.patch(prop, return_value=""): + fname = fig.canvas.manager.toolbar.save_figure() + assert fname is None + print("success") + + @_isolated_tk_test(success_count=1) def test_canvas_focus(): import tkinter as tk @@ -261,3 +281,52 @@ def test_figure(master): foreground="white") test_figure(root) print("success") + + +@_isolated_tk_test(success_count=1) +def test_dpi_change_triggers_resize(): + """ + Test that _update_device_pixel_ratio recalculates figure.size_inches + using the actual widget dimensions, so the render size matches the + visible canvas area even when constrained by a layout manager. + See issue #31126. + """ + import tkinter as tk + from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg + from matplotlib.figure import Figure + + root = tk.Tk() + root.geometry("400x300") + root.update_idletasks() + + fig = Figure(dpi=100) + fig.add_subplot(111) + + canvas = FigureCanvasTkAgg(fig, master=root) + canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True) + canvas.draw() + root.update_idletasks() + + actual_w = canvas.get_tk_widget().winfo_width() + actual_h = canvas.get_tk_widget().winfo_height() + assert actual_w > 0 and actual_h > 0 + + # Simulate a 2x DPI change through _update_device_pixel_ratio. + # Mock the platform-specific DPI query to return ratio=2.0. + with patch.object(sys, 'platform', 'linux'), \ + patch.object(canvas._tkcanvas, 'winfo_fpixels', + return_value=192.0): + canvas._update_device_pixel_ratio() + + # Verify the render size matches the actual widget size, NOT the + # inflated physical size from get_width_height(physical=True). + size = fig.get_size_inches() + render_w = round(size[0] * fig.dpi) + render_h = round(size[1] * fig.dpi) + assert abs(render_w - actual_w) <= 2, \ + f"render width {render_w} != actual width {actual_w}" + assert abs(render_h - actual_h) <= 2, \ + f"render height {render_h} != actual height {actual_h}" + + print("success") + root.destroy() diff --git a/lib/matplotlib/tests/test_backend_webagg.py b/lib/matplotlib/tests/test_backend_webagg.py index 1d6769494ef9..c63534ad20e3 100644 --- a/lib/matplotlib/tests/test_backend_webagg.py +++ b/lib/matplotlib/tests/test_backend_webagg.py @@ -1,8 +1,13 @@ import os import sys +from unittest.mock import MagicMock + import pytest import matplotlib.backends.backend_webagg_core +from matplotlib.backends.backend_webagg_core import ( + FigureCanvasWebAggCore, NavigationToolbar2WebAgg, +) from matplotlib.testing import subprocess_run_for_testing @@ -30,3 +35,46 @@ def test_webagg_fallback(backend): def test_webagg_core_no_toolbar(): fm = matplotlib.backends.backend_webagg_core.FigureManagerWebAgg assert fm._toolbar2_class is None + + +def test_toolbar_button_dispatch_allowlist(): + """Only declared toolbar items should be dispatched.""" + fig = MagicMock() + canvas = FigureCanvasWebAggCore(fig) + canvas.toolbar = MagicMock(spec=NavigationToolbar2WebAgg) + canvas.toolbar.toolitems = NavigationToolbar2WebAgg.toolitems + + # Valid toolbar action should be dispatched. + canvas.handle_toolbar_button({'name': 'home'}) + canvas.toolbar.home.assert_called_once() + + # Invalid names should be silently ignored. + canvas.toolbar.reset_mock() + canvas.handle_toolbar_button({'name': '__init__'}) + canvas.handle_toolbar_button({'name': 'not_a_real_button'}) + # No methods should have been called. + assert canvas.toolbar.method_calls == [] + + +@pytest.mark.parametrize("host, origin, allowed", [ + ("localhost:8988", "http://localhost:8988", True), + ("localhost:8988", "http://evil.com", False), + ("localhost:8988", "http://127.0.0.1:8988", False), + ("localhost:8988", "http://[::1]:8988", False), + ("127.0.0.1:8988", "http://127.0.0.1:8988", True), + ("127.0.0.1:8988", "http://localhost:8988", False), + ("127.0.0.1:8988", "http://[::1]:8988", False), + ("[::1]:8988", "http://[::1]:8988", True), + ("[::1]:8988", "http://[::2]:8988", False), + ("[::1]:8988", "http://localhost:8988", False), + ("[::1]:8988", "http://evil.com", False), +]) +def test_websocket_rejects_cross_origin(host, origin, allowed): + """Verify Tornado's default check_origin rejects cross-origin requests.""" + pytest.importorskip("tornado") + from matplotlib.backends.backend_webagg import WebAggApplication + + ws = WebAggApplication.WebSocket.__new__(WebAggApplication.WebSocket) + ws.request = MagicMock() + ws.request.headers = {"Host": host} + assert ws.check_origin(origin) is allowed diff --git a/lib/matplotlib/tests/test_backends_interactive.py b/lib/matplotlib/tests/test_backends_interactive.py index 05f59ce39fa4..188983816372 100644 --- a/lib/matplotlib/tests/test_backends_interactive.py +++ b/lib/matplotlib/tests/test_backends_interactive.py @@ -78,7 +78,10 @@ def _get_available_interactive_backends(): missing = [dep for dep in deps if not importlib.util.find_spec(dep)] if missing: reason = "{} cannot be imported".format(", ".join(missing)) - elif env["MPLBACKEND"] == "tkagg" and _is_linux_and_xdisplay_invalid: + elif _is_linux_and_xdisplay_invalid and ( + env["MPLBACKEND"] == "tkagg" + # Remove when https://github.com/wxWidgets/Phoenix/pull/2638 is out. + or env["MPLBACKEND"].startswith("wx")): reason = "$DISPLAY is unset" elif _is_linux_and_display_invalid: reason = "$DISPLAY and $WAYLAND_DISPLAY are unset" @@ -104,13 +107,7 @@ def _get_available_interactive_backends(): elif env["MPLBACKEND"].startswith('wx') and sys.platform == 'darwin': # ignore on macosx because that's currently broken (github #16849) marks.append(pytest.mark.xfail(reason='github #16849')) - elif (env['MPLBACKEND'] == 'tkagg' and - ('TF_BUILD' in os.environ or 'GITHUB_ACTION' in os.environ) and - sys.platform == 'darwin' and - sys.version_info[:2] < (3, 11) - ): - marks.append( # https://github.com/actions/setup-python/issues/649 - pytest.mark.xfail(reason='Tk version mismatch on Azure macOS CI')) + envs.append(({**env, 'BACKEND_DEPS': ','.join(deps)}, marks)) return envs @@ -124,6 +121,7 @@ def _get_testable_interactive_backends(): # Reasonable safe values for slower CI/Remote and local architectures. _test_timeout = 120 if is_ci_environment() else 20 +_retry_count = 3 if is_ci_environment() else 0 def _test_toolbar_button_la_mode_icon(fig): @@ -159,7 +157,7 @@ def _test_interactive_impl(): import matplotlib as mpl from matplotlib import pyplot as plt - from matplotlib.backend_bases import KeyEvent + from matplotlib.backend_bases import KeyEvent, FigureCanvasBase mpl.rcParams.update({ "webagg.open_in_browser": False, "webagg.port_retries": 1, @@ -210,6 +208,10 @@ def check_alt_backend(alt_backend): if fig.canvas.toolbar: # i.e toolbar2. fig.canvas.toolbar.draw_rubberband(None, 1., 1, 2., 2) + if backend == 'webagg' and sys.version_info >= (3, 14): + import asyncio + asyncio.set_event_loop(asyncio.new_event_loop()) + timer = fig.canvas.new_timer(1.) # Test that floats are cast to int. timer.add_callback(KeyEvent("key_press_event", fig.canvas, "q")._process) # Trigger quitting upon draw. @@ -217,30 +219,37 @@ def check_alt_backend(alt_backend): fig.canvas.mpl_connect("close_event", print) result = io.BytesIO() - fig.savefig(result, format='png') + fig.savefig(result, format='png', dpi=100) plt.show() # Ensure that the window is really closed. plt.pause(0.5) - # Test that saving works after interactive window is closed, but the figure - # is not deleted. + # When the figure is closed, its manager is removed and the canvas is reset to + # FigureCanvasBase. Saving should still be possible. + assert type(fig.canvas) == FigureCanvasBase, str(fig.canvas) result_after = io.BytesIO() - fig.savefig(result_after, format='png') + fig.savefig(result_after, format='png', dpi=100) - assert result.getvalue() == result_after.getvalue() + if backend.endswith("agg"): + # agg-based interactive backends should save the same image as a non-interactive + # figure + assert result.getvalue() == result_after.getvalue() @pytest.mark.parametrize("env", _get_testable_interactive_backends()) @pytest.mark.parametrize("toolbar", ["toolbar2", "toolmanager"]) -@pytest.mark.flaky(reruns=3) +@pytest.mark.flaky(reruns=_retry_count) def test_interactive_backend(env, toolbar): if env["MPLBACKEND"] == "macosx": if toolbar == "toolmanager": pytest.skip("toolmanager is not implemented for macosx.") if env["MPLBACKEND"] == "wx": pytest.skip("wx backend is deprecated; tests failed on appveyor") + if env["MPLBACKEND"] == "wxagg" and toolbar == "toolmanager": + pytest.skip("Temporarily deactivated: show() changes figure height " + "and thus fails the test") try: proc = _run_helper( _test_interactive_impl, @@ -279,10 +288,13 @@ def _test_thread_impl(): future = ThreadPoolExecutor().submit(fig.canvas.draw) plt.pause(0.5) # flush_events fails here on at least Tkagg (bpo-41176) future.result() # Joins the thread; rethrows any exception. + # stash the current canvas as closing the figure will reset the canvas on + # the figure + canvas = fig.canvas plt.close() # backend is responsible for flushing any events here if plt.rcParams["backend"].lower().startswith("wx"): # TODO: debug why WX needs this only on py >= 3.8 - fig.canvas.flush_events() + canvas.flush_events() _thread_safe_backends = _get_testable_interactive_backends() @@ -323,7 +335,7 @@ def _test_thread_impl(): @pytest.mark.parametrize("env", _thread_safe_backends) -@pytest.mark.flaky(reruns=3) +@pytest.mark.flaky(reruns=_retry_count) def test_interactive_thread_safety(env): proc = _run_helper(_test_thread_impl, timeout=_test_timeout, extra_env=env) assert proc.stdout.count("CloseEvent") == 1 @@ -449,6 +461,9 @@ def qt5_and_qt6_pairs(): yield from ([qt5, qt6], [qt6, qt5]) +@pytest.mark.skipif( + sys.platform == "linux" and not _c_internal_utils.display_is_valid(), + reason="$DISPLAY and $WAYLAND_DISPLAY are unset") @pytest.mark.parametrize('host, mpl', [*qt5_and_qt6_pairs()]) def test_cross_Qt_imports(host, mpl): try: @@ -608,7 +623,7 @@ def _test_number_of_draws_script(): @pytest.mark.parametrize("env", _blit_backends) # subprocesses can struggle to get the display, so rerun a few times -@pytest.mark.flaky(reruns=4) +@pytest.mark.flaky(reruns=_retry_count) def test_blitting_events(env): proc = _run_helper( _test_number_of_draws_script, timeout=_test_timeout, extra_env=env) @@ -620,17 +635,29 @@ def test_blitting_events(env): assert 0 < ndraws < 5 +def _fallback_check(): + import IPython.core.interactiveshell as ipsh + import matplotlib.pyplot + ipsh.InteractiveShell.instance() + matplotlib.pyplot.figure() + + +def test_fallback_to_different_backend(): + pytest.importorskip("IPython") + # Runs the process that caused the GH issue 23770 + # making sure that this doesn't crash + # since we're supposed to be switching to a different backend instead. + response = _run_helper(_fallback_check, timeout=_test_timeout) + + def _impl_test_interactive_timers(): # A timer with <1 millisecond gets converted to int and therefore 0 # milliseconds, which the mac framework interprets as singleshot. # We only want singleshot if we specify that ourselves, otherwise we want # a repeating timer - import os from unittest.mock import Mock import matplotlib.pyplot as plt - # increase pause duration on CI to let things spin up - # particularly relevant for gtk3cairo - pause_time = 2 if os.getenv("CI") else 0.5 + pause_time = 0.5 fig = plt.figure() plt.pause(pause_time) timer = fig.canvas.new_timer(0.1) @@ -642,7 +669,7 @@ def _impl_test_interactive_timers(): assert mock.call_count > 1 # Now turn it into a single shot timer and verify only one gets triggered - mock.call_count = 0 + mock.reset_mock() timer.single_shot = True timer.start() plt.pause(pause_time) diff --git a/lib/matplotlib/tests/test_bbox_tight.py b/lib/matplotlib/tests/test_bbox_tight.py index 5f2b397b650d..0bda0b6fbec3 100644 --- a/lib/matplotlib/tests/test_bbox_tight.py +++ b/lib/matplotlib/tests/test_bbox_tight.py @@ -8,11 +8,12 @@ import matplotlib.path as mpath import matplotlib.patches as mpatches from matplotlib.ticker import FuncFormatter +from mpl_toolkits.axes_grid1.inset_locator import inset_axes -@image_comparison(['bbox_inches_tight'], remove_text=True, +@image_comparison(['bbox_inches_tight'], remove_text=True, style='mpl20', savefig_kwarg={'bbox_inches': 'tight'}) -def test_bbox_inches_tight(): +def test_bbox_inches_tight(text_placeholders): #: Test that a figure saved using bbox_inches='tight' is clipped correctly data = [[66386, 174296, 75131, 577908, 32015], [58230, 381139, 78045, 99308, 160454], @@ -20,7 +21,8 @@ def test_bbox_inches_tight(): [78415, 81858, 150656, 193263, 69638], [139361, 331509, 343164, 781380, 52269]] - col_labels = row_labels = [''] * 5 + col_labels = ('Freeze', 'Wind', 'Flood', 'Quake', 'Hail') + row_labels = [f'{x} year' for x in (100, 50, 20, 10, 5)] rows = len(data) ind = np.arange(len(col_labels)) + 0.3 # the x locations for the groups @@ -30,13 +32,13 @@ def test_bbox_inches_tight(): # the bottom values for stacked bar chart fig, ax = plt.subplots(1, 1) for row in range(rows): - ax.bar(ind, data[row], width, bottom=yoff, align='edge', color='b') + ax.bar(ind, data[row], width, bottom=yoff, align='edge') yoff = yoff + data[row] - cell_text.append(['']) + cell_text.append([f'{x / 1000:1.1f}' for x in yoff]) plt.xticks([]) plt.xlim(0, 5) - plt.legend([''] * 5, loc=(1.2, 0.2)) - fig.legend([''] * 5, bbox_to_anchor=(0, 0.2), loc='lower left') + plt.legend(['1', '2', '3', '4', '5'], loc=(1.2, 0.2)) + fig.legend(['a', 'b', 'c', 'd', 'e'], bbox_to_anchor=(0, 0.2), loc='lower left') # Add a table at the bottom of the axes cell_text.reverse() plt.table(cellText=cell_text, rowLabels=row_labels, colLabels=col_labels, @@ -45,7 +47,7 @@ def test_bbox_inches_tight(): @image_comparison(['bbox_inches_tight_suptile_legend'], savefig_kwarg={'bbox_inches': 'tight'}, - tol=0.02 if platform.machine() == 'arm64' else 0) + tol=0 if platform.machine() == 'x86_64' else 0.02) def test_bbox_inches_tight_suptile_legend(): plt.plot(np.arange(10), label='a straight line') plt.legend(bbox_to_anchor=(0.9, 1), loc='upper left') @@ -108,7 +110,7 @@ def test_bbox_inches_tight_clipping(): plt.gcf().artists.append(patch) -@image_comparison(['bbox_inches_tight_raster'], +@image_comparison(['bbox_inches_tight_raster'], tol=0.15, # For Ghostscript 10.06+. remove_text=True, savefig_kwarg={'bbox_inches': 'tight'}) def test_bbox_inches_tight_raster(): """Test rasterization with tight_layout""" @@ -165,11 +167,25 @@ def test_noop_tight_bbox(): assert im.shape == (7, 10, 4) -@image_comparison(['bbox_inches_fixed_aspect'], extensions=['png'], - remove_text=True, savefig_kwarg={'bbox_inches': 'tight'}) +@image_comparison(['bbox_inches_fixed_aspect.png'], remove_text=True, + savefig_kwarg={'bbox_inches': 'tight'}) def test_bbox_inches_fixed_aspect(): with plt.rc_context({'figure.constrained_layout.use': True}): fig, ax = plt.subplots() ax.plot([0, 1]) ax.set_xlim(0, 1) ax.set_aspect('equal') + + +@image_comparison(['bbox_inches_inset_rasterized.pdf'], remove_text=True, + savefig_kwarg={'bbox_inches': 'tight'}, style='mpl20') +def test_bbox_inches_inset_rasterized(): + fig, ax = plt.subplots() + + arr = np.arange(100).reshape(10, 10) + im = ax.imshow(arr) + inset = inset_axes( + ax, width='10%', height='30%', loc='upper left', + bbox_to_anchor=(0.045, 0., 1, 1), bbox_transform=ax.transAxes) + + fig.colorbar(im, cax=inset, orientation='horizontal') diff --git a/lib/matplotlib/tests/test_bezier.py b/lib/matplotlib/tests/test_bezier.py index 65e2c616e738..ad5e5acfe49e 100644 --- a/lib/matplotlib/tests/test_bezier.py +++ b/lib/matplotlib/tests/test_bezier.py @@ -2,7 +2,38 @@ Tests specific to the bezier module. """ -from matplotlib.bezier import inside_circle, split_bezier_intersecting_with_closedpath +import pytest +import numpy as np +from numpy.testing import assert_allclose + +from matplotlib.bezier import ( + _real_roots_in_01, inside_circle, split_bezier_intersecting_with_closedpath +) + + +@pytest.mark.parametrize("roots, expected_in_01", [ + ([0.5], [0.5]), + ([0.25, 0.75], [0.25, 0.75]), + ([0.2, 0.5, 0.8], [0.2, 0.5, 0.8]), + ([0.1, 0.2, 0.3, 0.4], [0.1, 0.2, 0.3, 0.4]), + ([0.0, 0.5], [0.0, 0.5]), + ([0.5, 1.0], [0.5, 1.0]), + ([2.0], []), # outside [0, 1] + ([0.5, 2.0], [0.5]), # one in, one out + ([-1j, 1j], []), # complex roots + ([0.5, -1j, 1j], [0.5]), # mix of real and complex + ([0.3, 0.3], [0.3, 0.3]), # repeated root +]) +def test_real_roots_in_01(roots, expected_in_01): + roots = np.array(roots) + coeffs = np.poly(roots)[::-1] # np.poly gives descending, we need ascending + result = _real_roots_in_01(coeffs.real) + assert_allclose(result, expected_in_01, atol=1e-10) + + +@pytest.mark.parametrize("coeffs", [[5], [0, 0, 0]]) +def test_real_roots_in_01_no_roots(coeffs): + assert len(_real_roots_in_01(coeffs)) == 0 def test_split_bezier_with_large_values(): diff --git a/lib/matplotlib/tests/test_category.py b/lib/matplotlib/tests/test_category.py index fd4aec88b574..7917bb17ad59 100644 --- a/lib/matplotlib/tests/test_category.py +++ b/lib/matplotlib/tests/test_category.py @@ -246,6 +246,14 @@ def test_update_plot(self, plotter): axis_test(ax.xaxis, ['a', 'b', 'd', 'c']) axis_test(ax.yaxis, ['e', 'g', 'f', 'a', 'b', 'd']) + def test_update_plot_heterogenous_plotter(self): + ax = plt.figure().subplots() + ax.scatter(['a', 'b'], ['e', 'g']) + ax.plot(['a', 'b', 'd'], ['f', 'a', 'b']) + ax.bar(['b', 'c', 'd'], ['g', 'e', 'd']) + axis_test(ax.xaxis, ['a', 'b', 'd', 'c']) + axis_test(ax.yaxis, ['e', 'g', 'f', 'a', 'b', 'd']) + failing_test_cases = [("mixed", ['A', 3.14]), ("number integer", ['1', 1]), ("string integer", ['42', 42]), @@ -273,7 +281,7 @@ def test_mixed_type_update_exception(self, plotter, xdata): @mpl.style.context('default') -@check_figures_equal(extensions=["png"]) +@check_figures_equal() def test_overriding_units_in_plot(fig_test, fig_ref): from datetime import datetime diff --git a/lib/matplotlib/tests/test_cbook.py b/lib/matplotlib/tests/test_cbook.py index a8faa9be3782..2db0d66ccbb5 100644 --- a/lib/matplotlib/tests/test_cbook.py +++ b/lib/matplotlib/tests/test_cbook.py @@ -15,6 +15,7 @@ assert_array_almost_equal) import pytest +import matplotlib as mpl from matplotlib import _api, cbook import matplotlib.colors as mcolors from matplotlib.cbook import delete_masked_points, strip_math @@ -292,6 +293,127 @@ def test_callback_wrong_disconnect(self, pickle, cls): # check we still have callbacks registered self.is_not_empty() + @pytest.mark.parametrize('pickle', [True, False]) + @pytest.mark.parametrize('cls', [Hashable, Unhashable]) + def test_callback_disconnect_func(self, pickle, cls): + # ensure we start with an empty registry + self.is_empty() + + # create a class for testing + mini_me = cls() + + # test that we can add a callback + self.connect(self.signal, mini_me.dummy, pickle) + self.is_not_empty() + + # disconnect by function reference + self.callbacks.disconnect(mini_me.dummy, signal=self.signal) + + # check we now have no callbacks registered + self.is_empty() + + @pytest.mark.parametrize('pickle', [True, False]) + @pytest.mark.parametrize('cls', [Hashable, Unhashable]) + def test_callback_disconnect_func_wrong(self, pickle, cls): + # ensure we start with an empty registry + self.is_empty() + + # create a class for testing + mini_me = cls() + + # test that we can add a callback + self.connect(self.signal, mini_me.dummy, pickle) + self.is_not_empty() + + # try to disconnect with wrong signal - should do nothing + self.callbacks.disconnect(mini_me.dummy, signal='wrong_signal') + + # check we still have callbacks registered + self.is_not_empty() + + # try to disconnect with wrong function - should do nothing + mini_me2 = cls() + self.callbacks.disconnect(mini_me2.dummy, signal=self.signal) + + # check we still have callbacks registered + self.is_not_empty() + + def test_callback_disconnect_func_redefined(self): + # Test that redefining a function name doesn't affect disconnect. + # When you redefine a function, it creates a new function object, + # so disconnect should not disconnect the original. + self.is_empty() + + def func(): + pass + + self.callbacks.connect(self.signal, func) + self.is_not_empty() + + # Redefine func - this creates a new function object + def func(): + pass + + # Try to disconnect with the redefined function + self.callbacks.disconnect(func, signal=self.signal) + + # Original callback should still be registered + self.is_not_empty() + + @pytest.mark.parametrize('pickle', [True, False]) + @pytest.mark.parametrize('cls', [Hashable, Unhashable]) + def test_callback_disconnect_func_all_signals(self, pickle, cls): + # Test disconnecting a callback from all signals at once + self.is_empty() + + mini_me = cls() + + # Connect to multiple signals + self.callbacks.connect('signal1', mini_me.dummy) + self.callbacks.connect('signal2', mini_me.dummy) + assert len(list(self.callbacks._func_cid_map)) == 2 + + # Disconnect from all signals at once (no signal specified) + self.callbacks.disconnect(mini_me.dummy) + + # All callbacks should be removed + self.is_empty() + + def test_disconnect_cid_with_signal_raises(self): + # Passing signal with a cid should raise an error + self.is_empty() + cid = self.callbacks.connect(self.signal, lambda: None) + with pytest.raises(ValueError, match="signal cannot be specified"): + self.callbacks.disconnect(cid, signal=self.signal) + + @pytest.mark.parametrize('pickle', [True, False]) + @pytest.mark.parametrize('cls', [Hashable, Unhashable]) + def test_callback_disconnect_func_selective(self, pickle, cls): + # Test selectively disconnecting a callback from one signal + # while keeping it connected to another + self.is_empty() + + mini_me = cls() + + # Connect same function to multiple signals + self.callbacks.connect('signal1', mini_me.dummy) + self.callbacks.connect('signal2', mini_me.dummy) + assert len(list(self.callbacks._func_cid_map)) == 2 + + # Disconnect from only signal1 + self.callbacks.disconnect(mini_me.dummy, signal='signal1') + + # Should still have one callback registered (on signal2) + assert len(list(self.callbacks._func_cid_map)) == 1 + assert 'signal2' in self.callbacks.callbacks + assert 'signal1' not in self.callbacks.callbacks + + # Disconnect from signal2 + self.callbacks.disconnect(mini_me.dummy, signal='signal2') + + # Now all should be removed + self.is_empty() + @pytest.mark.parametrize('pickle', [True, False]) @pytest.mark.parametrize('cls', [Hashable, Unhashable]) def test_registration_on_non_empty_registry(self, pickle, cls): @@ -463,30 +585,59 @@ def test_sanitize_sequence(): assert k == cbook.sanitize_sequence(k) -fail_mapping: tuple[tuple[dict, dict], ...] = ( - ({'a': 1, 'b': 2}, {'alias_mapping': {'a': ['b']}}), - ({'a': 1, 'b': 2}, {'alias_mapping': {'a': ['a', 'b']}}), -) +def test_resize_sequence(): + a_list = [1, 2, 3] + arr = np.array([1, 2, 3]) + + # already same length: passthrough + assert cbook._resize_sequence(a_list, 3) is a_list + assert cbook._resize_sequence(arr, 3) is arr + + # shortening + assert cbook._resize_sequence(a_list, 2) == [1, 2] + assert_array_equal(cbook._resize_sequence(arr, 2), [1, 2]) + + # extending + assert cbook._resize_sequence(a_list, 5) == [1, 2, 3, 1, 2] + assert_array_equal(cbook._resize_sequence(arr, 5), [1, 2, 3, 1, 2]) + + +fail_mapping: list[tuple[dict, dict]] = [ + ({"a": 1, "b": 2}, {"a": ["b"]}), + ({"a": 1, "b": 2}, {"a": ["a", "b"]}), +] -pass_mapping: tuple[tuple[Any, dict, dict], ...] = ( +pass_mapping: list[tuple[Any, dict, dict]] = [ (None, {}, {}), - ({'a': 1, 'b': 2}, {'a': 1, 'b': 2}, {}), - ({'b': 2}, {'a': 2}, {'alias_mapping': {'a': ['a', 'b']}}), -) + ({"a": 1, "b": 2}, {"a": 1, "b": 2}, {}), + ({"b": 2}, {"a": 2}, {"a": ["a", "b"]}), +] -@pytest.mark.parametrize('inp, kwargs_to_norm', fail_mapping) -def test_normalize_kwargs_fail(inp, kwargs_to_norm): - with pytest.raises(TypeError), _api.suppress_matplotlib_deprecation_warning(): - cbook.normalize_kwargs(inp, **kwargs_to_norm) +@pytest.mark.parametrize('inp, alias_def', fail_mapping) +def test_normalize_kwargs_fail(inp, alias_def): + @_api.define_aliases(alias_def) + class Type(mpl.artist.Artist): + def get_a(self): return None -@pytest.mark.parametrize('inp, expected, kwargs_to_norm', - pass_mapping) -def test_normalize_kwargs_pass(inp, expected, kwargs_to_norm): - with _api.suppress_matplotlib_deprecation_warning(): - # No other warning should be emitted. - assert expected == cbook.normalize_kwargs(inp, **kwargs_to_norm) + with pytest.raises(TypeError): + cbook.normalize_kwargs(inp, Type) + + +@pytest.mark.parametrize('inp, expected, alias_def', pass_mapping) +def test_normalize_kwargs_pass(inp, expected, alias_def): + + @_api.define_aliases(alias_def) + class Type(mpl.artist.Artist): + def get_a(self): return None + + assert expected == cbook.normalize_kwargs(inp, Type) + old_alias_map = {} + for alias, prop in Type._alias_to_prop.items(): + old_alias_map.setdefault(prop, []).append(alias) + with pytest.warns(mpl.MatplotlibDeprecationWarning): + assert expected == cbook.normalize_kwargs(inp, old_alias_map) def test_warn_external(recwarn): @@ -605,6 +756,7 @@ class Dummy: g.join(*objs) assert set(list(g)[0]) == set(objs) assert set(g.get_siblings(a)) == set(objs) + assert a not in g.get_siblings(a, include_self=False) for other in objs[1:]: assert g.joined(a, other) @@ -983,6 +1135,7 @@ def __array__(self): torch_tensor = torch.Tensor(data) result = cbook._unpack_to_numpy(torch_tensor) + assert isinstance(result, np.ndarray) # compare results, do not check for identity: the latter would fail # if not mocked, and the implementation does not guarantee it # is the same Python object, just the same values. @@ -1011,6 +1164,7 @@ def __array__(self): jax_array = jax.Array(data) result = cbook._unpack_to_numpy(jax_array) + assert isinstance(result, np.ndarray) # compare results, do not check for identity: the latter would fail # if not mocked, and the implementation does not guarantee it # is the same Python object, just the same values. @@ -1040,6 +1194,38 @@ def __array__(self): tf_tensor = tensorflow.Tensor(data) result = cbook._unpack_to_numpy(tf_tensor) + assert isinstance(result, np.ndarray) + # compare results, do not check for identity: the latter would fail + # if not mocked, and the implementation does not guarantee it + # is the same Python object, just the same values. + assert_array_equal(result, data) + + +def test_unpack_to_numpy_from_mlx(): + """ + Test that mlx arrays are converted to NumPy arrays. + + We don't want to create a dependency on mlx in the test suite, so we mock it. + """ + class Array: + def __init__(self, data): + self.data = data + + def __array__(self): + return self.data + + # mlx is something peculiar + # class `array` is in `mlx.core` + mlx_core = ModuleType('mlx.core') + mlx_core.array = Array + + sys.modules['mlx.core'] = mlx_core + + data = np.arange(10) + mlx_array = mlx_core.array(data) + + result = cbook._unpack_to_numpy(mlx_array) + assert isinstance(result, np.ndarray) # compare results, do not check for identity: the latter would fail # if not mocked, and the implementation does not guarantee it # is the same Python object, just the same values. diff --git a/lib/matplotlib/tests/test_collections.py b/lib/matplotlib/tests/test_collections.py index 19af38f3b522..c0ac4ac28c8b 100644 --- a/lib/matplotlib/tests/test_collections.py +++ b/lib/matplotlib/tests/test_collections.py @@ -66,7 +66,7 @@ def generate_EventCollection_plot(): return ax, coll, props -@image_comparison(['EventCollection_plot__default']) +@image_comparison(['EventCollection_plot__default.png']) def test__EventCollection__get_props(): _, coll, props = generate_EventCollection_plot() # check that the default segments have the correct coordinates @@ -92,7 +92,7 @@ def test__EventCollection__get_props(): np.testing.assert_array_equal(color, props['color']) -@image_comparison(['EventCollection_plot__set_positions']) +@image_comparison(['EventCollection_plot__set_positions.png']) def test__EventCollection__set_positions(): splt, coll, props = generate_EventCollection_plot() new_positions = np.hstack([props['positions'], props['extra_positions']]) @@ -106,7 +106,7 @@ def test__EventCollection__set_positions(): splt.set_xlim(-1, 90) -@image_comparison(['EventCollection_plot__add_positions']) +@image_comparison(['EventCollection_plot__add_positions.png']) def test__EventCollection__add_positions(): splt, coll, props = generate_EventCollection_plot() new_positions = np.hstack([props['positions'], @@ -124,7 +124,7 @@ def test__EventCollection__add_positions(): splt.set_xlim(-1, 35) -@image_comparison(['EventCollection_plot__append_positions']) +@image_comparison(['EventCollection_plot__append_positions.png']) def test__EventCollection__append_positions(): splt, coll, props = generate_EventCollection_plot() new_positions = np.hstack([props['positions'], @@ -140,7 +140,7 @@ def test__EventCollection__append_positions(): splt.set_xlim(-1, 90) -@image_comparison(['EventCollection_plot__extend_positions']) +@image_comparison(['EventCollection_plot__extend_positions.png']) def test__EventCollection__extend_positions(): splt, coll, props = generate_EventCollection_plot() new_positions = np.hstack([props['positions'], @@ -156,7 +156,7 @@ def test__EventCollection__extend_positions(): splt.set_xlim(-1, 90) -@image_comparison(['EventCollection_plot__switch_orientation']) +@image_comparison(['EventCollection_plot__switch_orientation.png']) def test__EventCollection__switch_orientation(): splt, coll, props = generate_EventCollection_plot() new_orientation = 'vertical' @@ -173,7 +173,7 @@ def test__EventCollection__switch_orientation(): splt.set_xlim(0, 2) -@image_comparison(['EventCollection_plot__switch_orientation__2x']) +@image_comparison(['EventCollection_plot__switch_orientation__2x.png']) def test__EventCollection__switch_orientation_2x(): """ Check that calling switch_orientation twice sets the orientation back to @@ -194,7 +194,7 @@ def test__EventCollection__switch_orientation_2x(): splt.set_title('EventCollection: switch_orientation 2x') -@image_comparison(['EventCollection_plot__set_orientation']) +@image_comparison(['EventCollection_plot__set_orientation.png']) def test__EventCollection__set_orientation(): splt, coll, props = generate_EventCollection_plot() new_orientation = 'vertical' @@ -211,7 +211,7 @@ def test__EventCollection__set_orientation(): splt.set_xlim(0, 2) -@image_comparison(['EventCollection_plot__set_linelength']) +@image_comparison(['EventCollection_plot__set_linelength.png']) def test__EventCollection__set_linelength(): splt, coll, props = generate_EventCollection_plot() new_linelength = 15 @@ -226,7 +226,7 @@ def test__EventCollection__set_linelength(): splt.set_ylim(-20, 20) -@image_comparison(['EventCollection_plot__set_lineoffset']) +@image_comparison(['EventCollection_plot__set_lineoffset.png']) def test__EventCollection__set_lineoffset(): splt, coll, props = generate_EventCollection_plot() new_lineoffset = -5. @@ -242,9 +242,9 @@ def test__EventCollection__set_lineoffset(): @image_comparison([ - 'EventCollection_plot__set_linestyle', - 'EventCollection_plot__set_linestyle', - 'EventCollection_plot__set_linewidth', + 'EventCollection_plot__set_linestyle.png', + 'EventCollection_plot__set_linestyle.png', + 'EventCollection_plot__set_linewidth.png', ]) def test__EventCollection__set_prop(): for prop, value, expected in [ @@ -258,7 +258,7 @@ def test__EventCollection__set_prop(): splt.set_title(f'EventCollection: set_{prop}') -@image_comparison(['EventCollection_plot__set_color']) +@image_comparison(['EventCollection_plot__set_color.png']) def test__EventCollection__set_color(): splt, coll, _ = generate_EventCollection_plot() new_color = np.array([0, 1, 1, 1]) @@ -334,7 +334,7 @@ def test_add_collection(): @mpl.style.context('mpl20') -@check_figures_equal(extensions=['png']) +@check_figures_equal() def test_collection_log_datalim(fig_test, fig_ref): # Data limits should respect the minimum x/y when using log scale. x_vals = [4.38462e-6, 5.54929e-6, 7.02332e-6, 8.88889e-6, 1.12500e-5, @@ -391,7 +391,7 @@ def test_barb_limits(): @image_comparison(['EllipseCollection_test_image.png'], remove_text=True, - tol=0.021 if platform.machine() == 'arm64' else 0) + tol=0 if platform.machine() == 'x86_64' else 0.021) def test_EllipseCollection(): # Test basic functionality fig, ax = plt.subplots() @@ -408,7 +408,6 @@ def test_EllipseCollection(): ww, hh, aa, units='x', offsets=XY, offset_transform=ax.transData, facecolors='none') ax.add_collection(ec) - ax.autoscale_view() def test_EllipseCollection_setter_getter(): @@ -492,6 +491,28 @@ def test_polycollection_close(): ax.set_ylim3d(0, 4) +@check_figures_equal() +def test_scalarmap_change_cmap(fig_test, fig_ref): + # Ensure that changing the colormap of a 3D scatter after draw updates the colors. + + 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='jet') + fig_test.canvas.draw() + sc_test.set_cmap('viridis') + + # ref + ax_ref = fig_ref.add_subplot(111, projection='3d') + ax_ref.scatter(x, y, z, c=c, s=40, cmap='viridis') + + @image_comparison(['regularpolycollection_rotate.png'], remove_text=True) def test_regularpolycollection_rotate(): xx, yy = np.mgrid[:10, :10] @@ -503,8 +524,7 @@ def test_regularpolycollection_rotate(): col = mcollections.RegularPolyCollection( 4, sizes=(100,), rotation=alpha, offsets=[xy], offset_transform=ax.transData) - ax.add_collection(col, autolim=True) - ax.autoscale_view() + ax.add_collection(col) @image_comparison(['regularpolycollection_scale.png'], remove_text=True) @@ -532,7 +552,7 @@ def get_transform(self): circle_areas = [np.pi / 2] squares = SquareCollection( sizes=circle_areas, offsets=xy, offset_transform=ax.transData) - ax.add_collection(squares, autolim=True) + ax.add_collection(squares) ax.axis([-1, 1, -1, 1]) @@ -831,7 +851,7 @@ def test_collection_set_verts_array(): assert np.array_equal(ap._codes, atp._codes) -@check_figures_equal(extensions=["png"]) +@check_figures_equal() @pytest.mark.parametrize("kwargs", [{}, {"step": "pre"}]) def test_fill_between_poly_collection_set_data(fig_test, fig_ref, kwargs): t = np.linspace(0, 16) @@ -877,17 +897,24 @@ def test_collection_set_array(): def test_blended_collection_autolim(): - a = [1, 2, 4] - height = .2 + f, ax = plt.subplots() - xy_pairs = np.column_stack([np.repeat(a, 2), np.tile([0, height], len(a))]) - line_segs = xy_pairs.reshape([len(a), 2, 2]) + # sample data to give initial data limits + ax.plot([2, 3, 4], [0.4, 0.6, 0.5]) + np.testing.assert_allclose((ax.dataLim.xmin, ax.dataLim.xmax), (2, 4)) + data_ymin, data_ymax = ax.dataLim.ymin, ax.dataLim.ymax - f, ax = plt.subplots() + # LineCollection with vertical lines spanning the Axes vertical, using transAxes + x = [1, 2, 3, 4, 5] + vertical_lines = [np.array([[xi, 0], [xi, 1]]) for xi in x] trans = mtransforms.blended_transform_factory(ax.transData, ax.transAxes) - ax.add_collection(LineCollection(line_segs, transform=trans)) - ax.autoscale_view(scalex=True, scaley=False) - np.testing.assert_allclose(ax.get_xlim(), [1., 4.]) + ax.add_collection(LineCollection(vertical_lines, transform=trans)) + + # check that the x data limits are updated to include the LineCollection + np.testing.assert_allclose((ax.dataLim.xmin, ax.dataLim.xmax), (1, 5)) + # check that the y data limits are not updated (because they are not transData) + np.testing.assert_allclose((ax.dataLim.ymin, ax.dataLim.ymax), + (data_ymin, data_ymax)) def test_singleton_autolim(): @@ -1287,8 +1314,7 @@ def test_set_offset_units(): np.testing.assert_allclose(off0, sc.get_offsets()) -@image_comparison(baseline_images=["test_check_masked_offsets"], - extensions=["png"], remove_text=True, style="mpl20") +@image_comparison(["test_check_masked_offsets.png"], remove_text=True, style="mpl20") def test_check_masked_offsets(): # Check if masked data is respected by scatter # Ref: Issue #24545 @@ -1306,7 +1332,7 @@ def test_check_masked_offsets(): ax.scatter(unmasked_x, masked_y) -@check_figures_equal(extensions=["png"]) +@check_figures_equal() def test_masked_set_offsets(fig_ref, fig_test): x = np.ma.array([1, 2, 3, 4, 5], mask=[0, 0, 1, 1, 0]) y = np.arange(1, 6) @@ -1340,7 +1366,7 @@ def test_check_offsets_dtype(): @pytest.mark.parametrize('gapcolor', ['orange', ['r', 'k']]) -@check_figures_equal(extensions=['png']) +@check_figures_equal() def test_striped_lines(fig_test, fig_ref, gapcolor): ax_test = fig_test.add_subplot(111) ax_ref = fig_ref.add_subplot(111) @@ -1361,3 +1387,153 @@ def test_striped_lines(fig_test, fig_ref, gapcolor): for x, gcol, ls in zip(x, itertools.cycle(gapcolor), itertools.cycle(linestyles)): ax_ref.axvline(x, 0, 1, linewidth=20, linestyle=ls, gapcolor=gcol, alpha=0.5) + + +@check_figures_equal(extensions=['png', 'pdf', 'svg', 'eps']) +def test_hatch_linewidth(fig_test, fig_ref): + ax_test = fig_test.add_subplot() + ax_ref = fig_ref.add_subplot() + + lw = 2.0 + + polygons = [ + [(0.1, 0.1), (0.1, 0.4), (0.4, 0.4), (0.4, 0.1)], + [(0.6, 0.6), (0.6, 0.9), (0.9, 0.9), (0.9, 0.6)], + ] + ref = PolyCollection(polygons, hatch="x") + ref.set_hatch_linewidth(lw) + + with mpl.rc_context({"hatch.linewidth": lw}): + test = PolyCollection(polygons, hatch="x") + + ax_ref.add_collection(ref) + ax_test.add_collection(test) + + assert test.get_hatch_linewidth() == ref.get_hatch_linewidth() == lw + + +def test_collection_hatchcolor_inherit_logic(): + from matplotlib.collections import PathCollection + path = mpath.Path.unit_rectangle() + + edgecolors = ['purple', 'red', 'green', 'yellow'] + hatchcolors = ['orange', 'cyan', 'blue', 'magenta'] + with mpl.rc_context({'hatch.color': 'edge'}): + # edgecolor and hatchcolor is set + col = PathCollection([path], hatch='//', + edgecolor=edgecolors, hatchcolor=hatchcolors) + assert_array_equal(col.get_hatchcolor(), mpl.colors.to_rgba_array(hatchcolors)) + + # explicitly setting edgecolor and then hatchcolor + col = PathCollection([path], hatch='//') + col.set_edgecolor(edgecolors) + assert_array_equal(col.get_hatchcolor(), mpl.colors.to_rgba_array(edgecolors)) + col.set_hatchcolor(hatchcolors) + assert_array_equal(col.get_hatchcolor(), mpl.colors.to_rgba_array(hatchcolors)) + + # explicitly setting hatchcolor and then edgecolor + col = PathCollection([path], hatch='//') + col.set_hatchcolor(hatchcolors) + assert_array_equal(col.get_hatchcolor(), mpl.colors.to_rgba_array(hatchcolors)) + col.set_edgecolor(edgecolors) + assert_array_equal(col.get_hatchcolor(), mpl.colors.to_rgba_array(hatchcolors)) + + +def test_collection_hatchcolor_fallback_logic(): + from matplotlib.collections import PathCollection + path = mpath.Path.unit_rectangle() + + edgecolors = ['purple', 'red', 'green', 'yellow'] + hatchcolors = ['orange', 'cyan', 'blue', 'magenta'] + + # hatchcolor parameter should take precedence over rcParam + # When edgecolor is not set + with mpl.rc_context({'hatch.color': 'green'}): + col = PathCollection([path], hatch='//', hatchcolor=hatchcolors) + assert_array_equal(col.get_hatchcolor(), mpl.colors.to_rgba_array(hatchcolors)) + # When edgecolor is set + with mpl.rc_context({'hatch.color': 'green'}): + col = PathCollection([path], hatch='//', + edgecolor=edgecolors, hatchcolor=hatchcolors) + assert_array_equal(col.get_hatchcolor(), mpl.colors.to_rgba_array(hatchcolors)) + + # hatchcolor should not be overridden by edgecolor when + # hatchcolor parameter is not passed and hatch.color rcParam is set to a color + with mpl.rc_context({'hatch.color': 'green'}): + col = PathCollection([path], hatch='//') + assert_array_equal(col.get_hatchcolor(), mpl.colors.to_rgba_array('green')) + col.set_edgecolor(edgecolors) + assert_array_equal(col.get_hatchcolor(), mpl.colors.to_rgba_array('green')) + + # hatchcolor should match edgecolor when + # hatchcolor parameter is not passed and hatch.color rcParam is set to 'edge' + with mpl.rc_context({'hatch.color': 'edge'}): + col = PathCollection([path], hatch='//', edgecolor=edgecolors) + assert_array_equal(col.get_hatchcolor(), mpl.colors.to_rgba_array(edgecolors)) + # hatchcolor parameter is set to 'edge' + col = PathCollection([path], hatch='//', edgecolor=edgecolors, hatchcolor='edge') + assert_array_equal(col.get_hatchcolor(), mpl.colors.to_rgba_array(edgecolors)) + + # default hatchcolor should be used when hatchcolor parameter is not passed and + # hatch.color rcParam is set to 'edge' and edgecolor is not set + col = PathCollection([path], hatch='//') + assert_array_equal(col.get_hatchcolor(), + mpl.colors.to_rgba_array(mpl.rcParams['patch.edgecolor'])) + + +@pytest.mark.parametrize('backend', ['agg', 'pdf', 'svg', 'ps']) +def test_draw_path_collection_no_hatchcolor(backend): + from matplotlib.collections import PathCollection + path = mpath.Path.unit_rectangle() + + plt.switch_backend(backend) + fig, ax = plt.subplots() + renderer = fig._get_renderer() + + col = PathCollection([path], hatch='//') + ax.add_collection(col) + + gc = renderer.new_gc() + transform = mtransforms.IdentityTransform() + paths = col.get_paths() + transforms = col.get_transforms() + offsets = col.get_offsets() + offset_trf = col.get_offset_transform() + facecolors = col.get_facecolor() + edgecolors = col.get_edgecolor() + linewidths = col.get_linewidth() + linestyles = col.get_linestyle() + antialiaseds = col.get_antialiased() + urls = col.get_urls() + offset_position = "screen" + + renderer.draw_path_collection( + gc, transform, paths, transforms, offsets, offset_trf, + facecolors, edgecolors, linewidths, linestyles, + antialiaseds, urls, offset_position + ) + + +def test_third_party_backend_hatchcolors_arg_fallback(monkeypatch): + fig, ax = plt.subplots() + canvas = fig.canvas + renderer = canvas.get_renderer() + + # monkeypatch the `draw_path_collection` method to simulate a third-party backend + # that does not support the `hatchcolors` argument. + def mock_draw_path_collection(self, gc, master_transform, paths, all_transforms, + offsets, offset_trans, facecolors, edgecolors, + linewidths, linestyles, antialiaseds, urls, + offset_position): + pass + + monkeypatch.setattr(renderer, 'draw_path_collection', mock_draw_path_collection) + + # Create a PathCollection with hatch colors + from matplotlib.collections import PathCollection + path = mpath.Path.unit_rectangle() + coll = PathCollection([path], hatch='//', hatchcolor='red') + + ax.add_collection(coll) + + plt.draw() diff --git a/lib/matplotlib/tests/test_colorbar.py b/lib/matplotlib/tests/test_colorbar.py index 24eeab689424..77ff797be11d 100644 --- a/lib/matplotlib/tests/test_colorbar.py +++ b/lib/matplotlib/tests/test_colorbar.py @@ -152,14 +152,14 @@ def test_colorbar_extension_inverted_axis(orientation, extend, expected): assert len(cbar._extend_patches) == 1 +# TODO: tighten tolerance after baseline image is regenerated for text overhaul @pytest.mark.parametrize('use_gridspec', [True, False]) -@image_comparison(['cbar_with_orientation', - 'cbar_locationing', - 'double_cbar', - 'cbar_sharing', +@image_comparison(['cbar_with_orientation.png', + 'cbar_locationing.png', + 'double_cbar.png', + 'cbar_sharing.png', ], - extensions=['png'], remove_text=True, - savefig_kwarg={'dpi': 40}) + remove_text=True, savefig_kwarg={'dpi': 40}, tol=0.05) def test_colorbar_positioning(use_gridspec): # Remove this line when this test image is regenerated. plt.rcParams['pcolormesh.snap'] = False @@ -317,7 +317,7 @@ def test_remove_from_figure_cl(): def test_colorbarbase(): # smoke test from #3805 ax = plt.gca() - Colorbar(ax, cmap=plt.cm.bone) + Colorbar(ax, cmap=plt.colormaps["bone"]) def test_parentless_mappable(): @@ -332,11 +332,11 @@ def test_colorbar_closed_patch(): plt.rcParams['pcolormesh.snap'] = False fig = plt.figure(figsize=(8, 6)) - ax1 = fig.add_axes([0.05, 0.85, 0.9, 0.1]) - ax2 = fig.add_axes([0.1, 0.65, 0.75, 0.1]) - ax3 = fig.add_axes([0.05, 0.45, 0.9, 0.1]) - ax4 = fig.add_axes([0.05, 0.25, 0.9, 0.1]) - ax5 = fig.add_axes([0.05, 0.05, 0.9, 0.1]) + ax1 = fig.add_axes((0.05, 0.85, 0.9, 0.1)) + ax2 = fig.add_axes((0.1, 0.65, 0.75, 0.1)) + ax3 = fig.add_axes((0.05, 0.45, 0.9, 0.1)) + ax4 = fig.add_axes((0.05, 0.25, 0.9, 0.1)) + ax5 = fig.add_axes((0.05, 0.05, 0.9, 0.1)) cmap = mpl.colormaps["RdBu"].resampled(5) @@ -491,12 +491,13 @@ def test_colorbar_autotickslog(): pcm = ax[1].pcolormesh(X, Y, 10**Z, norm=LogNorm()) cbar2 = fig.colorbar(pcm, ax=ax[1], extend='both', orientation='vertical', shrink=0.4) + + fig.draw_without_rendering() # note only -12 to +12 are visible - np.testing.assert_almost_equal(cbar.ax.yaxis.get_ticklocs(), - 10**np.arange(-16., 16.2, 4.)) - # note only -24 to +24 are visible - np.testing.assert_almost_equal(cbar2.ax.yaxis.get_ticklocs(), - 10**np.arange(-24., 25., 12.)) + np.testing.assert_equal(np.log10(cbar.ax.yaxis.get_ticklocs()), + [-18, -12, -6, 0, +6, +12, +18]) + np.testing.assert_equal(np.log10(cbar2.ax.yaxis.get_ticklocs()), + [-36, -12, 12, +36]) def test_colorbar_get_ticks(): @@ -597,7 +598,7 @@ def test_colorbar_renorm(): norm = LogNorm(z.min(), z.max()) im.set_norm(norm) np.testing.assert_allclose(cbar.ax.yaxis.get_majorticklocs(), - np.logspace(-10, 7, 18)) + np.logspace(-9, 6, 16)) # note that set_norm removes the FixedLocator... assert np.isclose(cbar.vmin, z.min()) cbar.set_ticks([1, 2, 3]) @@ -730,7 +731,8 @@ def test_colorbar_label(): assert cbar3.ax.get_xlabel() == 'horizontal cbar' -@image_comparison(['colorbar_keeping_xlabel.png'], style='mpl20') +# TODO: tighten tolerance after baseline image is regenerated for text overhaul +@image_comparison(['colorbar_keeping_xlabel.png'], style='mpl20', tol=0.03) def test_keeping_xlabel(): # github issue #23398 - xlabels being ignored in colorbar axis arr = np.arange(25).reshape((5, 5)) @@ -844,16 +846,16 @@ def test_colorbar_change_lim_scale(): pc = ax[1].pcolormesh(np.arange(100).reshape(10, 10)+1) cb = fig.colorbar(pc, ax=ax[1], extend='both') - cb.ax.set_ylim([20, 90]) + cb.ax.set_ylim(20, 90) -@check_figures_equal(extensions=["png"]) +@check_figures_equal() def test_axes_handles_same_functions(fig_ref, fig_test): # prove that cax and cb.ax are functionally the same for nn, fig in enumerate([fig_ref, fig_test]): ax = fig.add_subplot() pc = ax.pcolormesh(np.ones(300).reshape(10, 30)) - cax = fig.add_axes([0.9, 0.1, 0.03, 0.8]) + cax = fig.add_axes((0.9, 0.1, 0.03, 0.8)) cb = fig.colorbar(pc, cax=cax) if nn == 0: caxx = cax @@ -893,11 +895,12 @@ def test_twoslope_colorbar(): fig.colorbar(pc) -@check_figures_equal(extensions=["png"]) -def test_remove_cb_whose_mappable_has_no_figure(fig_ref, fig_test): - ax = fig_test.add_subplot() - cb = fig_test.colorbar(cm.ScalarMappable(), cax=ax) +def test_remove_cb_whose_mappable_has_no_figure(): + fig, ax = plt.subplots() + assert fig.get_axes() != [] + cb = fig.colorbar(cm.ScalarMappable(), cax=ax) cb.remove() + assert fig.get_axes() == [] def test_aspects(): @@ -945,9 +948,10 @@ def test_proportional_colorbars(): levels = [-1.25, -0.5, -0.125, 0.125, 0.5, 1.25] cmap = mcolors.ListedColormap( - ['0.3', '0.5', 'white', 'lightblue', 'steelblue']) - cmap.set_under('darkred') - cmap.set_over('crimson') + ['0.3', '0.5', 'white', 'lightblue', 'steelblue'], + under='darkred', + over='crimson', + ) norm = mcolors.BoundaryNorm(levels, cmap.N) extends = ['neither', 'both'] @@ -1177,7 +1181,7 @@ def test_title_text_loc(): cb.ax.spines['outline'].get_window_extent().ymax) -@check_figures_equal(extensions=["png"]) +@check_figures_equal() def test_passing_location(fig_ref, fig_test): ax_ref = fig_ref.add_subplot() im = ax_ref.imshow([[0, 1], [2, 3]]) @@ -1208,7 +1212,7 @@ def test_colorbar_errors(kwargs, error, message): fig.colorbar(im, **kwargs) -def test_colorbar_axes_parmeters(): +def test_colorbar_axes_parameters(): fig, ax = plt.subplots(2) im = ax[0].imshow([[0, 1], [2, 3]]) # colorbar should accept any form of axes sequence: diff --git a/lib/matplotlib/tests/test_colors.py b/lib/matplotlib/tests/test_colors.py index cc6cb1bb11a7..24a2f31f7594 100644 --- a/lib/matplotlib/tests/test_colors.py +++ b/lib/matplotlib/tests/test_colors.py @@ -7,7 +7,9 @@ from PIL import Image import pytest import base64 +import platform +from numpy.lib import recfunctions as rfn from numpy.testing import assert_array_equal, assert_array_almost_equal from matplotlib import cbook, cm @@ -20,7 +22,7 @@ import matplotlib.scale as mscale from matplotlib.rcsetup import cycler from matplotlib.testing.decorators import image_comparison, check_figures_equal -from matplotlib.colors import is_color_like, to_rgba_array +from matplotlib.colors import is_color_like, to_rgba_array, ListedColormap @pytest.mark.parametrize('N, result', [ @@ -51,13 +53,9 @@ def test_resampled(): colorlist[:, 1] = 0.2 colorlist[:, 2] = np.linspace(1, 0, n) colorlist[:, 3] = 0.7 - lsc = mcolors.LinearSegmentedColormap.from_list('lsc', colorlist) - lc = mcolors.ListedColormap(colorlist) - # Set some bad values for testing too - for cmap in [lsc, lc]: - cmap.set_under('r') - cmap.set_over('g') - cmap.set_bad('b') + lsc = mcolors.LinearSegmentedColormap.from_list( + 'lsc', colorlist, under='red', over='green', bad='blue') + lc = mcolors.ListedColormap(colorlist, under='red', over='green', bad='blue') lsc3 = lsc.resampled(3) lc3 = lc.resampled(3) expected = np.array([[0.0, 0.2, 1.0, 0.7], @@ -74,6 +72,12 @@ def test_resampled(): assert_array_almost_equal(lc(np.nan), lc3(np.nan)) +def test_monochrome(): + assert mcolors.ListedColormap(["red"]).monochrome + assert mcolors.ListedColormap(["red"] * 5).monochrome + assert not mcolors.ListedColormap(["red", "green"]).monochrome + + def test_colormaps_get_cmap(): cr = mpl.colormaps @@ -102,22 +106,24 @@ def test_double_register_builtin_cmap(): def test_colormap_copy(): - cmap = plt.cm.Reds + cmap = plt.colormaps["Reds"] copied_cmap = copy.copy(cmap) with np.errstate(invalid='ignore'): ret1 = copied_cmap([-1, 0, .5, 1, np.nan, np.inf]) cmap2 = copy.copy(copied_cmap) - cmap2.set_bad('g') + with pytest.warns(PendingDeprecationWarning): + cmap2.set_bad('g') with np.errstate(invalid='ignore'): ret2 = copied_cmap([-1, 0, .5, 1, np.nan, np.inf]) assert_array_equal(ret1, ret2) # again with the .copy method: - cmap = plt.cm.Reds + cmap = plt.colormaps["Reds"] copied_cmap = cmap.copy() with np.errstate(invalid='ignore'): ret1 = copied_cmap([-1, 0, .5, 1, np.nan, np.inf]) cmap2 = copy.copy(copied_cmap) - cmap2.set_bad('g') + with pytest.warns(PendingDeprecationWarning): + cmap2.set_bad('g') with np.errstate(invalid='ignore'): ret2 = copied_cmap([-1, 0, .5, 1, np.nan, np.inf]) assert_array_equal(ret1, ret2) @@ -131,7 +137,8 @@ def test_colormap_equals(): # But the same data should be equal assert cm_copy == cmap # Change the copy - cm_copy.set_bad('y') + with pytest.warns(PendingDeprecationWarning): + cm_copy.set_bad('y') assert cm_copy != cmap # Make sure we can compare different sizes without failure cm_copy._lut = cm_copy._lut[:10, :] @@ -215,6 +222,44 @@ def test_colormap_return_types(): assert cmap(x2d).shape == x2d.shape + (4,) +def test_ListedColormap_bad_under_over(): + cmap = mcolors.ListedColormap(["r", "g", "b"], bad="c", under="m", over="y") + assert mcolors.same_color(cmap.get_bad(), "c") + assert mcolors.same_color(cmap.get_under(), "m") + assert mcolors.same_color(cmap.get_over(), "y") + + +def test_LinearSegmentedColormap_bad_under_over(): + cdict = { + 'red': [(0., 0., 0.), (0.5, 1., 1.), (1., 1., 1.)], + 'green': [(0., 0., 0.), (0.25, 0., 0.), (0.75, 1., 1.), (1., 1., 1.)], + 'blue': [(0., 0., 0.), (0.5, 0., 0.), (1., 1., 1.)], + } + cmap = mcolors.LinearSegmentedColormap("lsc", cdict, bad="c", under="m", over="y") + assert mcolors.same_color(cmap.get_bad(), "c") + assert mcolors.same_color(cmap.get_under(), "m") + assert mcolors.same_color(cmap.get_over(), "y") + + +def test_LinearSegmentedColormap_from_list_bad_under_over(): + cmap = mcolors.LinearSegmentedColormap.from_list( + "lsc", ["r", "g", "b"], bad="c", under="m", over="y") + assert mcolors.same_color(cmap.get_bad(), "c") + assert mcolors.same_color(cmap.get_under(), "m") + assert mcolors.same_color(cmap.get_over(), "y") + + +def test_colormap_with_alpha(): + cmap = ListedColormap(["red", "green", ("blue", 0.8)]) + cmap2 = cmap.with_alpha(0.5) + # color is the same: + vals = [0, 0.5, 1] # numeric positions that map to the listed colors + assert_array_equal(cmap(vals)[:, :3], cmap2(vals)[:, :3]) + # alpha of cmap2 is changed: + assert_array_equal(cmap(vals)[:, 3], [1, 1, 0.8]) + assert_array_equal(cmap2(vals)[:, 3], [0.5, 0.5, 0.5]) + + def test_BoundaryNorm(): """ GitHub issue #1258: interpolation was failing with numpy @@ -325,9 +370,7 @@ def test_BoundaryNorm(): assert_array_equal(mynorm(x), ref) # Without interpolation - cmref = mcolors.ListedColormap(['blue', 'red']) - cmref.set_over('black') - cmref.set_under('white') + cmref = mcolors.ListedColormap(['blue', 'red'], under='white', over='black') cmshould = mcolors.ListedColormap(['white', 'blue', 'red', 'black']) assert mcolors.same_color(cmref.get_over(), 'black') @@ -349,8 +392,7 @@ def test_BoundaryNorm(): assert_array_equal(cmshould(mynorm(x)), cmref(refnorm(x))) # Just min - cmref = mcolors.ListedColormap(['blue', 'red']) - cmref.set_under('white') + cmref = mcolors.ListedColormap(['blue', 'red'], under='white') cmshould = mcolors.ListedColormap(['white', 'blue', 'red']) assert mcolors.same_color(cmref.get_under(), 'white') @@ -367,8 +409,7 @@ def test_BoundaryNorm(): assert_array_equal(cmshould(mynorm(x)), cmref(refnorm(x))) # Just max - cmref = mcolors.ListedColormap(['blue', 'red']) - cmref.set_over('black') + cmref = mcolors.ListedColormap(['blue', 'red'], over='black') cmshould = mcolors.ListedColormap(['blue', 'red', 'black']) assert mcolors.same_color(cmref.get_over(), 'black') @@ -591,8 +632,9 @@ def inverse(x): norm = mcolors.FuncNorm((forward, inverse), vmin=0.1, vmax=10) lognorm = mcolors.LogNorm(vmin=0.1, vmax=10) assert_array_almost_equal(norm([0.2, 5, 10]), lognorm([0.2, 5, 10])) - assert_array_almost_equal(norm.inverse([0.2, 5, 10]), - lognorm.inverse([0.2, 5, 10])) + # use assert_allclose here for rtol on large numbers + np.testing.assert_allclose(norm.inverse([0.2, 5, 10]), + lognorm.inverse([0.2, 5, 10])) def test_TwoSlopeNorm_autoscale(): @@ -805,17 +847,16 @@ def test_cmap_and_norm_from_levels_and_colors(): ax.tick_params(labelleft=False, labelbottom=False) -@image_comparison(baseline_images=['boundarynorm_and_colorbar'], - extensions=['png'], tol=1.0) +@image_comparison(['boundarynorm_and_colorbar.png'], tol=1.0) def test_boundarynorm_and_colorbarbase(): # Remove this line when this test image is regenerated. plt.rcParams['pcolormesh.snap'] = False # Make a figure and axes with dimensions as desired. fig = plt.figure() - ax1 = fig.add_axes([0.05, 0.80, 0.9, 0.15]) - ax2 = fig.add_axes([0.05, 0.475, 0.9, 0.15]) - ax3 = fig.add_axes([0.05, 0.15, 0.9, 0.15]) + ax1 = fig.add_axes((0.05, 0.80, 0.9, 0.15)) + ax2 = fig.add_axes((0.05, 0.475, 0.9, 0.15)) + ax3 = fig.add_axes((0.05, 0.15, 0.9, 0.15)) # Set the colormap and bounds bounds = [-1, 2, 5, 7, 12, 15] @@ -882,7 +923,7 @@ def test_cmap_and_norm_from_levels_and_colors2(): for extend, i1, cases in tests: cmap, norm = mcolors.from_levels_and_colors(levels, colors[0:i1], extend=extend) - cmap.set_bad(bad) + cmap = cmap.with_extremes(bad=bad) for d_val, expected_color in cases.items(): if d_val == masked_value: d_val = np.ma.array([1], mask=True) @@ -906,6 +947,11 @@ def test_rgb_hsv_round_trip(): tt, mcolors.rgb_to_hsv(mcolors.hsv_to_rgb(tt))) +def test_rgb_to_hsv_int(): + # Test that int rgb values (still range 0-1) are processed correctly. + assert_array_equal(mcolors.rgb_to_hsv((0, 1, 0)), (1/3, 1, 1)) # green + + def test_autoscale_masked(): # Test for #2336. Previously fully masked data would trigger a ValueError. data = np.ma.masked_all((12, 20)) @@ -944,7 +990,7 @@ def test_light_source_shading_default(): y, x = np.mgrid[-1.2:1.2:8j, -1.2:1.2:8j] z = 10 * np.cos(x**2 + y**2) - cmap = plt.cm.copper + cmap = plt.colormaps["copper"] ls = mcolors.LightSource(315, 45) rgb = ls.shade(z, cmap) @@ -995,7 +1041,7 @@ def test_light_source_shading_empty_mask(): z0 = 10 * np.cos(x**2 + y**2) z1 = np.ma.array(z0) - cmap = plt.cm.copper + cmap = plt.colormaps["copper"] ls = mcolors.LightSource(315, 45) rgb0 = ls.shade(z0, cmap) rgb1 = ls.shade(z1, cmap) @@ -1016,7 +1062,7 @@ def test_light_source_masked_shading(): z = np.ma.masked_greater(z, 9.9) - cmap = plt.cm.copper + cmap = plt.colormaps["copper"] ls = mcolors.LightSource(315, 45) rgb = ls.shade(z, cmap) @@ -1159,8 +1205,8 @@ def test_pandas_iterable(pd): # a single color lst = ['red', 'blue', 'green'] s = pd.Series(lst) - cm1 = mcolors.ListedColormap(lst, N=5) - cm2 = mcolors.ListedColormap(s, N=5) + cm1 = mcolors.ListedColormap(lst) + cm2 = mcolors.ListedColormap(s) assert_array_equal(cm1.colors, cm2.colors) @@ -1185,10 +1231,18 @@ def test_colormap_reversing(name): def test_has_alpha_channel(): assert mcolors._has_alpha_channel((0, 0, 0, 0)) assert mcolors._has_alpha_channel([1, 1, 1, 1]) + assert mcolors._has_alpha_channel('#fff8') + assert mcolors._has_alpha_channel('#0f0f0f80') + assert mcolors._has_alpha_channel(('r', 0.5)) + assert mcolors._has_alpha_channel(([1, 1, 1, 1], None)) assert not mcolors._has_alpha_channel('blue') # 4-char string! assert not mcolors._has_alpha_channel('0.25') assert not mcolors._has_alpha_channel('r') assert not mcolors._has_alpha_channel((1, 0, 0)) + assert not mcolors._has_alpha_channel('#fff') + assert not mcolors._has_alpha_channel('#0f0f0f') + assert not mcolors._has_alpha_channel(('r', None)) + assert not mcolors._has_alpha_channel(([1, 1, 1], None)) def test_cn(): @@ -1376,7 +1430,7 @@ def test_scalarmappable_nan_to_rgba(bytes): # Out-of-range fail x[1, 0, 0] = 42 - with pytest.raises(ValueError, match='0..1 range'): + with pytest.raises(ValueError, match=r'\[0,1\] range'): sm.to_rgba(x[..., :3], bytes=bytes) @@ -1489,7 +1543,8 @@ def test_get_under_over_bad(): def test_non_mutable_get_values(kind): cmap = copy.copy(mpl.colormaps['viridis']) init_value = getattr(cmap, f'get_{kind}')() - getattr(cmap, f'set_{kind}')('k') + with pytest.warns(PendingDeprecationWarning): + getattr(cmap, f'set_{kind}')('k') black_value = getattr(cmap, f'get_{kind}')() assert np.all(black_value == [0, 0, 0, 1]) assert not np.all(init_value == black_value) @@ -1553,6 +1608,23 @@ def test_norm_deepcopy(): assert norm2.vmin == norm.vmin +def test_set_clim_emits_single_callback(): + data = np.array([[1, 2], [3, 4]]) + fig, ax = plt.subplots() + image = ax.imshow(data, cmap='viridis') + + callback = unittest.mock.Mock() + image.norm.callbacks.connect('changed', callback) + + callback.assert_not_called() + + # Call set_clim() to update the limits + image.set_clim(1, 5) + + # Assert that only one "changed" callback is sent after calling set_clim() + callback.assert_called_once() + + def test_norm_callback(): increment = unittest.mock.Mock(return_value=None) @@ -1573,7 +1645,7 @@ def test_norm_callback(): assert increment.call_count == 2 # We only want autoscale() calls to send out one update signal - increment.call_count = 0 + increment.reset_mock() norm.autoscale([0, 1, 2]) assert increment.call_count == 1 @@ -1635,7 +1707,8 @@ def test_color_sequences(): assert plt.color_sequences is matplotlib.color_sequences # same registry assert list(plt.color_sequences) == [ 'tab10', 'tab20', 'tab20b', 'tab20c', 'Pastel1', 'Pastel2', 'Paired', - 'Accent', 'Dark2', 'Set1', 'Set2', 'Set3', 'petroff10'] + 'Accent', 'okabe_ito', 'Dark2', 'Set1', 'Set2', 'Set3', 'petroff6', + 'petroff8', 'petroff10'] assert len(plt.color_sequences['tab10']) == 10 assert len(plt.color_sequences['tab20']) == 20 @@ -1728,3 +1801,450 @@ def test_colorizer_vmin_vmax(): assert ca.vmax == 3.0 assert ca.norm.vmin == 1.0 assert ca.norm.vmax == 3.0 + + +def test_LinearSegmentedColormap_from_list_color_alpha_tuple(): + """ + GitHub issue #29042: A bug in 'from_list' causes an error + when passing a tuple (str, float) where the string is a + color name or grayscale value and float is an alpha value. + """ + colors = [("red", 0.3), ("0.42", 0.1), "green"] + cmap = mcolors.LinearSegmentedColormap.from_list("lsc", colors, N=3) + assert_array_almost_equal(cmap([.0, 0.5, 1.]), to_rgba_array(colors)) + + +@pytest.mark.parametrize("colors", + [[(0.42, "blue"), (.1, .1, .1, .1)], + ["blue", (0.42, "red")], + ["blue", (.1, .1, .1, .1), ("red", 2)], + [(0, "red"), (1.1, "blue")], + [(0.52, "red"), (0.42, "blue")]]) +def test_LinearSegmentedColormap_from_list_invalid_inputs(colors): + with pytest.raises(ValueError): + mcolors.LinearSegmentedColormap.from_list("lsc", colors) + + +def test_LinearSegmentedColormap_from_list_value_color_tuple(): + value_color_tuples = [(0, "red"), (0.6, "blue"), (1, "green")] + cmap = mcolors.LinearSegmentedColormap.from_list("lsc", value_color_tuples, N=11) + assert_array_almost_equal( + cmap([value for value, _ in value_color_tuples]), + to_rgba_array([color for _, color in value_color_tuples]), + ) + + +@image_comparison(['test_norm_abc.png'], remove_text=True, + tol=0 if platform.machine() == 'x86_64' else 0.05) +def test_norm_abc(): + + class CustomHalfNorm(mcolors.Norm): + def __init__(self): + super().__init__() + + @property + def vmin(self): + return 0 + + @property + def vmax(self): + return 1 + + @property + def clip(self): + return False + + def __call__(self, value, clip=None): + return value / 2 + + def inverse(self, value): + return 2 * value + + def autoscale(self, A): + pass + + def autoscale_None(self, A): + pass + + def scaled(self): + return True + + @property + def n_components(self): + return 1 + + fig, axes = plt.subplots(2,2) + + r = np.linspace(-1, 3, 16*16).reshape((16,16)) + norm = CustomHalfNorm() + colorizer = mpl.colorizer.Colorizer(cmap='viridis', norm=norm) + c = axes[0,0].imshow(r, colorizer=colorizer) + axes[0,1].pcolor(r, colorizer=colorizer) + axes[1,0].contour(r, colorizer=colorizer) + axes[1,1].contourf(r, colorizer=colorizer) + + +def test_close_error_name(): + with pytest.raises( + KeyError, + match=( + "'grays' is not a valid value for colormap. " + "Did you mean one of ['gray', 'Grays', 'gray_r']?" + )): + matplotlib.colormaps["grays"] + + +def test_multi_norm_creation(): + # tests for mcolors.MultiNorm + + # test wrong input + with pytest.raises(ValueError, + match="MultiNorm must be assigned an iterable"): + mcolors.MultiNorm("linear") + with pytest.raises(ValueError, + match="MultiNorm must be assigned at least one"): + mcolors.MultiNorm([]) + with pytest.raises(ValueError, + match="MultiNorm must be assigned an iterable"): + mcolors.MultiNorm(None) + with pytest.raises(ValueError, + match="not a valid"): + mcolors.MultiNorm(["linear", "bad_norm_name"]) + with pytest.raises(ValueError, + match="Each norm assigned to MultiNorm"): + mcolors.MultiNorm(["linear", object()]) + + norm = mpl.colors.MultiNorm(['linear', 'linear']) + + +def test_multi_norm_call_vmin_vmax(): + # test get vmin, vmax + norm = mpl.colors.MultiNorm(['linear', 'log']) + norm.vmin = (1, 1) + norm.vmax = (2, 2) + assert norm.vmin == (1, 1) + assert norm.vmax == (2, 2) + + with pytest.raises(ValueError, match="Expected an iterable of length 2"): + norm.vmin = 1 + with pytest.raises(ValueError, match="Expected an iterable of length 2"): + norm.vmax = 1 + with pytest.raises(ValueError, match="Expected an iterable of length 2"): + norm.vmin = (1, 2, 3) + with pytest.raises(ValueError, match="Expected an iterable of length 2"): + norm.vmax = (1, 2, 3) + + +def test_multi_norm_call_clip_inverse(): + # test get vmin, vmax + norm = mpl.colors.MultiNorm(['linear', 'log']) + norm.vmin = (1, 1) + norm.vmax = (2, 2) + + # test call with clip + assert_array_equal(norm([3, 3], clip=[False, False]), [2.0, 1.584962500721156]) + assert_array_equal(norm([3, 3], clip=[True, True]), [1.0, 1.0]) + assert_array_equal(norm([3, 3], clip=[True, False]), [1.0, 1.584962500721156]) + norm.clip = [False, False] + assert_array_equal(norm([3, 3]), [2.0, 1.584962500721156]) + norm.clip = [True, True] + assert_array_equal(norm([3, 3]), [1.0, 1.0]) + norm.clip = [True, False] + assert_array_equal(norm([3, 3]), [1.0, 1.584962500721156]) + norm.clip = [True, True] + + with pytest.raises(ValueError, match="Expected an iterable of length 2"): + norm.clip = True + with pytest.raises(ValueError, match="Expected an iterable of length 2"): + norm.clip = [True, False, True] + with pytest.raises(ValueError, match="Expected an iterable of length 2"): + norm([3, 3], clip=True) + with pytest.raises(ValueError, match="Expected an iterable of length 2"): + norm([3, 3], clip=[True, True, True]) + + # test inverse + assert_array_almost_equal(norm.inverse([0.5, 0.5849625007211562]), [1.5, 1.5]) + + +def test_multi_norm_autoscale(): + norm = mpl.colors.MultiNorm(['linear', 'log']) + # test autoscale + norm.autoscale([[0, 1, 2, 3], [0.1, 1, 2, 3]]) + assert_array_equal(norm.vmin, [0, 0.1]) + assert_array_equal(norm.vmax, [3, 3]) + + # test autoscale_none + norm0 = mcolors.TwoSlopeNorm(2, vmin=0, vmax=None) + norm = mcolors.MultiNorm([norm0, 'linear'], vmax=[None, 50]) + norm.autoscale_None([[1, 2, 3, 4, 5], [-50, 1, 0, 1, 500]]) + assert_array_equal(norm([5, 0]), [1, 0.5]) + assert_array_equal(norm.vmin, (0, -50)) + assert_array_equal(norm.vmax, (5, 50)) + + +def test_mult_norm_call_types(): + mn = mpl.colors.MultiNorm(['linear', 'linear']) + mn.vmin = (-2, -2) + mn.vmax = (2, 2) + + vals = np.arange(6).reshape((3,2)) + target = np.ma.array([(0.5, 0.75), + (1., 1.25), + (1.5, 1.75)]) + + # test structured array as input + from_mn = mn(rfn.unstructured_to_structured(vals)) + assert_array_almost_equal(from_mn, + target.T) + + # test list of arrays as input + assert_array_almost_equal(mn(list(vals.T)), + list(target.T)) + # test list of floats as input + assert_array_almost_equal(mn(list(vals[0])), + list(target[0])) + # test tuple of arrays as input + assert_array_almost_equal(mn(tuple(vals.T)), + list(target.T)) + + # np.arrays of shapes that are compatible + assert_array_almost_equal(mn(np.zeros(2)), + 0.5*np.ones(2)) + assert_array_almost_equal(mn(np.zeros((2, 3))), + 0.5*np.ones((2, 3))) + assert_array_almost_equal(mn(np.zeros((2, 3, 4))), + 0.5*np.ones((2, 3, 4))) + + # test with NoNorm, list as input + mn_no_norm = mpl.colors.MultiNorm(['linear', mcolors.NoNorm()]) + no_norm_out = mn_no_norm(list(vals.T)) + assert_array_almost_equal(no_norm_out, + [[0., 0.5, 1.], + [1, 3, 5]]) + assert no_norm_out[0].dtype == np.dtype('float64') + assert no_norm_out[1].dtype == vals.dtype + + # test with NoNorm, structured array as input + mn_no_norm = mpl.colors.MultiNorm(['linear', mcolors.NoNorm()]) + no_norm_out = mn_no_norm(rfn.unstructured_to_structured(vals)) + assert_array_almost_equal(no_norm_out, + [[0., 0.5, 1.], + [1, 3, 5]]) + + # test single int as input + with pytest.raises(ValueError, + match="component as input, but got 1 instead"): + mn(1) + + # test list of incompatible size + with pytest.raises(ValueError, + match="but got a sequence with 3 elements"): + mn([3, 2, 1]) + + # last axis matches, len(data.shape) > 2 + with pytest.raises(ValueError, + match=(r"`data_as_list = \[data\[..., i\] for i in " + r"range\(data.shape\[-1\]\)\]`")): + mn(np.zeros((3, 3, 2))) + + # last axis matches, len(data.shape) == 2 + with pytest.raises(ValueError, + match=r"You can use `data_transposed = data.T` to convert"): + mn(np.zeros((3, 2))) + + # incompatible arrays where no relevant axis matches + for data in [np.zeros(3), np.zeros((3, 2, 3))]: + with pytest.raises(ValueError, + match=r"but got a sequence with 3 elements"): + mn(data) + + # test incompatible class + with pytest.raises(ValueError, + match="but got 5, data) + mdata = mcolorizer._ensure_multivariate_data(data, 2) + assert np.all(mdata["f0"].mask[:2] == 0) + assert np.all(mdata["f0"].mask[2:] == 1) + assert np.all(mdata["f1"].mask[:2] == 0) + assert np.all(mdata["f1"].mask[2:] == 1) + + # test tuple of data + data = [0, 1] + mdata = mcolorizer._ensure_multivariate_data(data, 2) + assert mdata.shape == () + + # test wrong input size + data = [[0, 1]] + with pytest.raises(ValueError, match="must contain complex numbers"): + mcolorizer._ensure_multivariate_data(data, 2) + data = [[0, 1]] + with pytest.raises(ValueError, match="have a first dimension 3"): + mcolorizer._ensure_multivariate_data(data, 3) + + # test input of ints as list of lists + data = [[0, 0, 0], [1, 1, 1]] + mdata = mcolorizer._ensure_multivariate_data(data, 2) + assert mdata.shape == (3,) + assert mdata.dtype.fields['f0'][0] == np.int_ + assert mdata.dtype.fields['f1'][0] == np.int_ + + # test input of floats, ints as tuple of lists + data = ([0.0, 0.0], [1, 1]) + mdata = mcolorizer._ensure_multivariate_data(data, 2) + assert mdata.shape == (2,) + assert mdata.dtype.fields['f0'][0] == np.float64 + assert mdata.dtype.fields['f1'][0] == np.int_ + + # test input of array of floats + data = np.array([[0.0, 0, 0], [1, 1, 1]]) + mdata = mcolorizer._ensure_multivariate_data(data, 2) + assert mdata.shape == (3,) + assert mdata.dtype.fields['f0'][0] == np.float64 + assert mdata.dtype.fields['f1'][0] == np.float64 + + # test more input dims + data = np.zeros((3, 4, 5, 6)) + mdata = mcolorizer._ensure_multivariate_data(data, 3) + assert mdata.shape == (4, 5, 6) + + +def test_colorizer_multinorm_implicit(): + ca = mcolorizer.Colorizer('BiOrangeBlue') + ca.vmin = (0, 0) + ca.vmax = (1, 1) + + # test call with two single values + data = [0.1, 0.2] + res = (0.098039, 0.149020, 0.2, 1.0) + assert_array_almost_equal(ca.to_rgba(data), res) + + # test call with two 1d arrays + data = [[0.1, 0.2], [0.3, 0.4]] + res = [[0.09803922, 0.19803922, 0.29803922, 1.], + [0.2, 0.3, 0.4, 1.]] + assert_array_almost_equal(ca.to_rgba(data), res) + + # test call with two 2d arrays + data = [np.linspace(0, 1, 12).reshape(3, 4), + np.linspace(1, 0, 12).reshape(3, 4)] + res = np.array([[[0., 0.5, 1., 1.], + [0.09019608, 0.5, 0.90980392, 1.], + [0.18039216, 0.5, 0.81960784, 1.], + [0.27058824, 0.5, 0.72941176, 1.]], + [[0.36470588, 0.5, 0.63529412, 1.], + [0.45490196, 0.5, 0.54509804, 1.], + [0.54509804, 0.5, 0.45490196, 1.], + [0.63529412, 0.5, 0.36470588, 1.]], + [[0.72941176, 0.5, 0.27058824, 1.], + [0.81960784, 0.5, 0.18039216, 1.], + [0.90980392, 0.5, 0.09019608, 1.], + [1., 0.5, 0., 1.]]]) + assert_array_almost_equal(ca.to_rgba(data), res) + + with pytest.raises(ValueError, match=("This MultiNorm has 2 components, " + "but got a sequence with 3 elements")): + ca.to_rgba([0.1, 0.2, 0.3]) + with pytest.raises(ValueError, match=("This MultiNorm has 2 components, " + "but got a sequence with 1 elements")): + ca.to_rgba([[0.1]]) + + # test multivariate + ca = mcolorizer.Colorizer('3VarAddA') + ca.vmin = (-0.1, -0.2, -0.3) + ca.vmax = (0.1, 0.2, 0.3) + + data = [0.1, 0.1, 0.1] + res = (0.712612, 0.896847, 0.954494, 1.0) + assert_array_almost_equal(ca.to_rgba(data), res) + + +def test_colorizer_multinorm_explicit(): + + with pytest.raises(ValueError, match="MultiNorm must be assigned"): + ca = mcolorizer.Colorizer('BiOrangeBlue', 'linear') + + with pytest.raises(TypeError, + match=("'norm' must be an instance of matplotlib.colors.Norm" + ", str or None, not a list")): + ca = mcolorizer.Colorizer('viridis', ['linear', 'linear']) + + with pytest.raises(ValueError, + match=("Invalid norm for multivariate colormap with 2 inputs")): + ca = mcolorizer.Colorizer('BiOrangeBlue', ['linear', 'linear', 'log']) + + # valid explicit construction + ca = mcolorizer.Colorizer('BiOrangeBlue', [mcolors.Normalize(), 'log']) + ca.vmin = (0, 0.01) + ca.vmax = (1, 1) + + # test call with two single values + data = [0.1, 0.2] + res = (0.098039, 0.374510, 0.65098, 1.) + assert_array_almost_equal(ca.to_rgba(data), res) + + +def test_invalid_cmap_n_components_zero(): + class CustomColormap(mcolors.Colormap): + def __init__(self): + super().__init__("custom") + self.n_variates = 0 + + with pytest.raises(ValueError, match='`n_variates` >= 1'): + ca = mcolorizer.Colorizer(CustomColormap()) + + +def test_colorizer_bivar_cmap(): + ca = mcolorizer.Colorizer('BiOrangeBlue', [mcolors.Normalize(), 'log']) + + with pytest.raises(ValueError, match='The colormap viridis'): + ca.cmap = 'viridis' + + cartist = mcolorizer.ColorizingArtist(ca) + cartist.set_array(np.zeros((2, 4, 4))) + + with pytest.raises(ValueError, match='Invalid data entry for multivariate'): + cartist.set_array(np.zeros((3, 4, 4))) + + dt = np.dtype([('x', 'f4'), ('', 'object')]) + with pytest.raises(TypeError, match='converted to a sequence of floats'): + cartist.set_array(np.zeros((2, 4, 4), dtype=dt)) + + with pytest.raises(ValueError, match='all variates must have same shape'): + cartist.set_array((np.zeros(3), np.zeros(4))) + + # ensure masked value is propagated from input + a = np.arange(3) + cartist.set_array((a, np.ma.masked_where(a > 1, a))) + assert np.all(cartist.get_array()['f0'].mask == np.array([0, 0, 0], dtype=bool)) + assert np.all(cartist.get_array()['f1'].mask == np.array([0, 0, 1], dtype=bool)) + + # test clearing data + cartist.set_array(None) + cartist.get_array() is None + + +def test_colorizer_multivar_cmap(): + ca = mcolorizer.Colorizer('3VarAddA', [mcolors.Normalize(), + mcolors.Normalize(), + 'log']) + cartist = mcolorizer.ColorizingArtist(ca) + cartist.set_array(np.zeros((3, 5, 5))) + with pytest.raises(ValueError, match='Complex numbers are incompatible with'): + cartist.set_array(np.zeros((5, 5), dtype='complex128')) diff --git a/lib/matplotlib/tests/test_compare_images.py b/lib/matplotlib/tests/test_compare_images.py index 6023f3d05468..96b76f790ccd 100644 --- a/lib/matplotlib/tests/test_compare_images.py +++ b/lib/matplotlib/tests/test_compare_images.py @@ -1,11 +1,14 @@ from pathlib import Path import shutil +import numpy as np import pytest from pytest import approx +from matplotlib import _image from matplotlib.testing.compare import compare_images from matplotlib.testing.decorators import _image_directories +from matplotlib.testing.exceptions import ImageComparisonFailure # Tests of the image comparison algorithm. @@ -71,3 +74,27 @@ def test_image_comparison_expect_rms(im1, im2, tol, expect_rms, tmp_path, else: assert results is not None assert results['rms'] == approx(expect_rms, abs=1e-4) + + +def test_invalid_input(): + img = np.zeros((16, 16, 4), dtype=np.uint8) + + with pytest.raises(ImageComparisonFailure, + match='must be 3-dimensional, but is 2-dimensional'): + _image.calculate_rms_and_diff(img[:, :, 0], img) + with pytest.raises(ImageComparisonFailure, + match='must be 3-dimensional, but is 5-dimensional'): + _image.calculate_rms_and_diff(img, img[:, :, :, np.newaxis, np.newaxis]) + with pytest.raises(ImageComparisonFailure, + match='must be RGB or RGBA but has depth 2'): + _image.calculate_rms_and_diff(img[:, :, :2], img) + + with pytest.raises(ImageComparisonFailure, + match=r'expected size: \(16, 16, 4\) actual size \(8, 16, 4\)'): + _image.calculate_rms_and_diff(img, img[:8, :, :]) + with pytest.raises(ImageComparisonFailure, + match=r'expected size: \(16, 16, 4\) actual size \(16, 6, 4\)'): + _image.calculate_rms_and_diff(img, img[:, :6, :]) + with pytest.raises(ImageComparisonFailure, + match=r'expected size: \(16, 16, 4\) actual size \(16, 16, 3\)'): + _image.calculate_rms_and_diff(img, img[:, :, :3]) diff --git a/lib/matplotlib/tests/test_constrainedlayout.py b/lib/matplotlib/tests/test_constrainedlayout.py index e42e2ee9bfd8..91aaa2fd9172 100644 --- a/lib/matplotlib/tests/test_constrainedlayout.py +++ b/lib/matplotlib/tests/test_constrainedlayout.py @@ -11,6 +11,11 @@ from matplotlib import gridspec, ticker +pytestmark = [ + pytest.mark.usefixtures('text_placeholders') +] + + def example_plot(ax, fontsize=12, nodec=False): ax.plot([1, 2]) ax.locator_params(nbins=3) @@ -36,7 +41,7 @@ def example_pcolor(ax, fontsize=12): return pcm -@image_comparison(['constrained_layout1.png']) +@image_comparison(['constrained_layout1.png'], style='mpl20') def test_constrained_layout1(): """Test constrained_layout for a single subplot""" fig = plt.figure(layout="constrained") @@ -44,7 +49,7 @@ def test_constrained_layout1(): example_plot(ax, fontsize=24) -@image_comparison(['constrained_layout2.png']) +@image_comparison(['constrained_layout2.png'], style='mpl20') def test_constrained_layout2(): """Test constrained_layout for 2x2 subplots""" fig, axs = plt.subplots(2, 2, layout="constrained") @@ -52,7 +57,7 @@ def test_constrained_layout2(): example_plot(ax, fontsize=24) -@image_comparison(['constrained_layout3.png']) +@image_comparison(['constrained_layout3.png'], style='mpl20') def test_constrained_layout3(): """Test constrained_layout for colorbars with subplots""" @@ -66,7 +71,7 @@ def test_constrained_layout3(): fig.colorbar(pcm, ax=ax, pad=pad) -@image_comparison(['constrained_layout4.png']) +@image_comparison(['constrained_layout4.png'], style='mpl20') def test_constrained_layout4(): """Test constrained_layout for a single colorbar with subplots""" @@ -76,7 +81,7 @@ def test_constrained_layout4(): fig.colorbar(pcm, ax=axs, pad=0.01, shrink=0.6) -@image_comparison(['constrained_layout5.png'], tol=0.002) +@image_comparison(['constrained_layout5.png'], style='mpl20') def test_constrained_layout5(): """ Test constrained_layout for a single colorbar with subplots, @@ -91,12 +96,9 @@ def test_constrained_layout5(): location='bottom') -@image_comparison(['constrained_layout6.png'], tol=0.002) +@image_comparison(['constrained_layout6.png'], style='mpl20') def test_constrained_layout6(): """Test constrained_layout for nested gridspecs""" - # Remove this line when this test image is regenerated. - plt.rcParams['pcolormesh.snap'] = False - fig = plt.figure(layout="constrained") gs = fig.add_gridspec(1, 2, figure=fig) gsl = gs[0].subgridspec(2, 2) @@ -154,7 +156,7 @@ def test_constrained_layout7(): fig.draw_without_rendering() -@image_comparison(['constrained_layout8.png']) +@image_comparison(['constrained_layout8.png'], style='mpl20') def test_constrained_layout8(): """Test for gridspecs that are not completely full""" @@ -182,7 +184,7 @@ def test_constrained_layout8(): fig.colorbar(pcm, ax=axs, pad=0.01, shrink=0.6) -@image_comparison(['constrained_layout9.png']) +@image_comparison(['constrained_layout9.png'], style='mpl20') def test_constrained_layout9(): """Test for handling suptitle and for sharex and sharey""" @@ -197,8 +199,8 @@ def test_constrained_layout9(): fig.suptitle('Test Suptitle', fontsize=28) -@image_comparison(['constrained_layout10.png'], - tol=0.032 if platform.machine() == 'arm64' else 0) +@image_comparison(['constrained_layout10.png'], style='mpl20', + tol=0 if platform.machine() == 'x86_64' else 0.032) def test_constrained_layout10(): """Test for handling legend outside axis""" fig, axs = plt.subplots(2, 2, layout="constrained") @@ -207,7 +209,7 @@ def test_constrained_layout10(): ax.legend(loc='center left', bbox_to_anchor=(0.8, 0.5)) -@image_comparison(['constrained_layout11.png']) +@image_comparison(['constrained_layout11.png'], style='mpl20') def test_constrained_layout11(): """Test for multiple nested gridspecs""" @@ -227,7 +229,7 @@ def test_constrained_layout11(): example_plot(ax, fontsize=9) -@image_comparison(['constrained_layout11rat.png']) +@image_comparison(['constrained_layout11rat.png'], style='mpl20') def test_constrained_layout11rat(): """Test for multiple nested gridspecs with width_ratios""" @@ -247,7 +249,7 @@ def test_constrained_layout11rat(): example_plot(ax, fontsize=9) -@image_comparison(['constrained_layout12.png']) +@image_comparison(['constrained_layout12.png'], style='mpl20') def test_constrained_layout12(): """Test that very unbalanced labeling still works.""" fig = plt.figure(layout="constrained", figsize=(6, 8)) @@ -269,7 +271,7 @@ def test_constrained_layout12(): ax.set_xlabel('x-label') -@image_comparison(['constrained_layout13.png'], tol=2.e-2) +@image_comparison(['constrained_layout13.png'], style='mpl20') def test_constrained_layout13(): """Test that padding works.""" fig, axs = plt.subplots(2, 2, layout="constrained") @@ -281,7 +283,7 @@ def test_constrained_layout13(): fig.get_layout_engine().set(w_pad=24./72., h_pad=24./72.) -@image_comparison(['constrained_layout14.png']) +@image_comparison(['constrained_layout14.png'], style='mpl20') def test_constrained_layout14(): """Test that padding works.""" fig, axs = plt.subplots(2, 2, layout="constrained") @@ -293,7 +295,7 @@ def test_constrained_layout14(): hspace=0.2, wspace=0.2) -@image_comparison(['constrained_layout15.png']) +@image_comparison(['constrained_layout15.png'], style='mpl20') def test_constrained_layout15(): """Test that rcparams work.""" mpl.rcParams['figure.constrained_layout.use'] = True @@ -302,15 +304,15 @@ def test_constrained_layout15(): example_plot(ax, fontsize=12) -@image_comparison(['constrained_layout16.png']) +@image_comparison(['constrained_layout16.png'], style='mpl20') def test_constrained_layout16(): """Test ax.set_position.""" fig, ax = plt.subplots(layout="constrained") example_plot(ax, fontsize=12) - ax2 = fig.add_axes([0.2, 0.2, 0.4, 0.4]) + ax2 = fig.add_axes((0.2, 0.2, 0.4, 0.4)) -@image_comparison(['constrained_layout17.png']) +@image_comparison(['constrained_layout17.png'], style='mpl20') def test_constrained_layout17(): """Test uneven gridspecs""" fig = plt.figure(layout="constrained") @@ -355,7 +357,7 @@ def test_constrained_layout20(): img = np.hypot(gx, gx[:, None]) fig = plt.figure() - ax = fig.add_axes([0, 0, 1, 1]) + ax = fig.add_axes((0, 0, 1, 1)) mesh = ax.pcolormesh(gx, gx, img[:-1, :-1]) fig.colorbar(mesh) @@ -435,7 +437,7 @@ def test_hidden_axes(): extents1 = np.copy(axs[0, 0].get_position().extents) np.testing.assert_allclose( - extents1, [0.045552, 0.543288, 0.47819, 0.982638], rtol=1e-5) + extents1, [0.046918, 0.541204, 0.477409, 0.980555], rtol=1e-5) def test_colorbar_align(): @@ -641,7 +643,7 @@ def test_compressed1(): fig.draw_without_rendering() pos = axs[0, 0].get_position() - np.testing.assert_allclose(pos.x0, 0.2344, atol=1e-3) + np.testing.assert_allclose(pos.x0, 0.2381, atol=1e-2) pos = axs[0, 1].get_position() np.testing.assert_allclose(pos.x1, 0.7024, atol=1e-3) @@ -655,11 +657,11 @@ def test_compressed1(): fig.draw_without_rendering() pos = axs[0, 0].get_position() - np.testing.assert_allclose(pos.x0, 0.06195, atol=1e-3) - np.testing.assert_allclose(pos.y1, 0.8537, atol=1e-3) + np.testing.assert_allclose(pos.x0, 0.05653, atol=1e-3) + np.testing.assert_allclose(pos.y1, 0.8603, atol=1e-2) pos = axs[1, 2].get_position() - np.testing.assert_allclose(pos.x1, 0.8618, atol=1e-3) - np.testing.assert_allclose(pos.y0, 0.1934, atol=1e-3) + np.testing.assert_allclose(pos.x1, 0.8728, atol=1e-3) + np.testing.assert_allclose(pos.y0, 0.1808, atol=1e-2) def test_compressed_suptitle(): @@ -675,7 +677,7 @@ def test_compressed_suptitle(): title = fig.suptitle("Title") fig.draw_without_rendering() - assert title.get_position()[1] == pytest.approx(0.7457, abs=1e-3) + assert title.get_position()[1] == pytest.approx(0.7491, abs=1e-3) title = fig.suptitle("Title", y=0.98) fig.draw_without_rendering() @@ -686,6 +688,77 @@ def test_compressed_suptitle(): assert title.get_position()[1] == 0.98 +@image_comparison(['test_compressed_suptitle_colorbar.png'], style='mpl20') +def test_compressed_suptitle_colorbar(): + """Test that colorbars align with axes in compressed layout with suptitle.""" + arr = np.arange(100).reshape((10, 10)) + fig, axs = plt.subplots(ncols=2, figsize=(4, 2), layout='compressed') + + im0 = axs[0].imshow(arr) + im1 = axs[1].imshow(arr) + + cb0 = plt.colorbar(im0, ax=axs[0]) + cb1 = plt.colorbar(im1, ax=axs[1]) + + fig.suptitle('Title') + + # Verify colorbar heights match axes heights + # After layout, colorbar should have same height as parent axes + fig.canvas.draw() + + for ax, cb in zip(axs, [cb0, cb1]): + ax_pos = ax.get_position() + cb_pos = cb.ax.get_position() + + # Check that colorbar height matches axes height (within tolerance) + # Note: We check the actual rendered positions, not the bbox + assert abs(cb_pos.height - ax_pos.height) < 0.01, \ + f"Colorbar height {cb_pos.height} doesn't match axes height {ax_pos.height}" + + # Also verify vertical alignment (y0 and y1 should match) + assert abs(cb_pos.y0 - ax_pos.y0) < 0.01, \ + f"Colorbar y0 {cb_pos.y0} doesn't match axes y0 {ax_pos.y0}" + assert abs(cb_pos.y1 - ax_pos.y1) < 0.01, \ + f"Colorbar y1 {cb_pos.y1} doesn't match axes y1 {ax_pos.y1}" + + +@image_comparison(['test_compressed_supylabel_colorbar.png'], style='mpl20') +def test_compressed_supylabel_colorbar(): + """ + Test that horizontal colorbars align with axes + in compressed layout with supylabel. + """ + arr = np.arange(100).reshape((10, 10)) + fig, axs = plt.subplots(nrows=2, figsize=(3, 4), layout='compressed') + + im0 = axs[0].imshow(arr) + im1 = axs[1].imshow(arr) + + cb0 = plt.colorbar(im0, ax=axs[0], orientation='horizontal') + cb1 = plt.colorbar(im1, ax=axs[1], orientation='horizontal') + + fig.supylabel('Title') + + # Verify colorbar widths match axes widths + # After layout, colorbar should have same width as parent axes + fig.canvas.draw() + + for ax, cb in zip(axs, [cb0, cb1]): + ax_pos = ax.get_position() + cb_pos = cb.ax.get_position() + + # Check that colorbar width matches axes width (within tolerance) + # Note: We check the actual rendered positions, not the bbox + assert abs(cb_pos.width - ax_pos.width) < 0.01, \ + f"Colorbar width {cb_pos.width} doesn't match axes width {ax_pos.width}" + + # Also verify horizontal alignment (x0 and x1 should match) + assert abs(cb_pos.x0 - ax_pos.x0) < 0.01, \ + f"Colorbar x0 {cb_pos.x0} doesn't match axes x0 {ax_pos.x0}" + assert abs(cb_pos.x1 - ax_pos.x1) < 0.01, \ + f"Colorbar x1 {cb_pos.x1} doesn't match axes x1 {ax_pos.x1}" + + @pytest.mark.parametrize('arg, state', [ (True, True), (False, False), @@ -719,3 +792,23 @@ def test_layout_leak(): gc.collect() assert not any(isinstance(obj, mpl._layoutgrid.LayoutGrid) for obj in gc.get_objects()) + + +def test_submerged_subfig(): + """ + Test that the submerged margin logic does not get called multiple times + on same axes if it is already in a subfigure + """ + fig = plt.figure(figsize=(4, 5), layout='constrained') + figures = fig.subfigures(3, 1) + axs = [] + for f in figures.flatten(): + gs = f.add_gridspec(2, 2) + for i in range(2): + axs += [f.add_subplot(gs[i, 0])] + axs[-1].plot() + f.add_subplot(gs[:, 1]).plot() + fig.draw_without_rendering() + for ax in axs[1:]: + assert np.allclose(ax.get_position().bounds[-1], + axs[0].get_position().bounds[-1], atol=1e-6) diff --git a/lib/matplotlib/tests/test_container.py b/lib/matplotlib/tests/test_container.py index 1e4577c518ae..b7dfe1196685 100644 --- a/lib/matplotlib/tests/test_container.py +++ b/lib/matplotlib/tests/test_container.py @@ -1,4 +1,5 @@ import numpy as np +from numpy.testing import assert_array_equal import matplotlib.pyplot as plt @@ -35,3 +36,43 @@ def test_nonstring_label(): # Test for #26824 plt.bar(np.arange(10), np.random.rand(10), label=1) plt.legend() + + +def test_barcontainer_position_centers__bottoms__tops(): + fig, ax = plt.subplots() + pos = [1, 2, 4] + bottoms = np.array([1, 5, 3]) + heights = np.array([2, 3, 4]) + + container = ax.bar(pos, heights, bottom=bottoms) + assert_array_equal(container.position_centers, pos) + assert_array_equal(container.bottoms, bottoms) + assert_array_equal(container.tops, bottoms + heights) + + container = ax.barh(pos, heights, left=bottoms) + assert_array_equal(container.position_centers, pos) + assert_array_equal(container.bottoms, bottoms) + assert_array_equal(container.tops, bottoms + heights) + + +def test_piecontainer_remove(): + fig, ax = plt.subplots() + pie = ax.pie([2, 3], labels=['foo', 'bar'], autopct="%1.0f%%") + ax.pie_label(pie, ['baz', 'qux']) + assert len(ax.patches) == 2 + assert len(ax.texts) == 6 + + pie.remove() + assert not ax.patches + assert not ax.texts + + +def test_piecontainer_unpack_backcompat(): + fig, ax = plt.subplots() + wedges, texts, autotexts = ax.pie( + [2, 3], labels=['foo', 'bar'], autopct="%1.0f%%", labeldistance=None) + + assert len(wedges) == 2 + assert isinstance(texts, list) + assert not texts + assert len(autotexts) == 2 diff --git a/lib/matplotlib/tests/test_contour.py b/lib/matplotlib/tests/test_contour.py index e0ea82973af7..e242c219df10 100644 --- a/lib/matplotlib/tests/test_contour.py +++ b/lib/matplotlib/tests/test_contour.py @@ -86,7 +86,7 @@ def test_contour_Nlevels(): assert (cs1.levels == cs2.levels).all() -@check_figures_equal(extensions=['png']) +@check_figures_equal() def test_contour_set_paths(fig_test, fig_ref): cs_test = fig_test.subplots().contour([[0, 1], [1, 2]]) cs_ref = fig_ref.subplots().contour([[1, 0], [2, 1]]) @@ -127,8 +127,9 @@ def test_contour_manual_moveto(): assert clabels[0].get_text() == "0" -@image_comparison(['contour_disconnected_segments'], - remove_text=True, style='mpl20', extensions=['png']) +# TODO: tighten tolerance after baseline image is regenerated for text overhaul +@image_comparison(['contour_disconnected_segments.png'], + remove_text=True, style='mpl20', tol=0.01) def test_contour_label_with_disconnected_segments(): x, y = np.mgrid[-1:1:21j, -1:1:21j] z = 1 / np.sqrt(0.01 + (x + 0.3) ** 2 + y ** 2) @@ -140,7 +141,7 @@ def test_contour_label_with_disconnected_segments(): @image_comparison(['contour_manual_colors_and_levels.png'], remove_text=True, - tol=0.018 if platform.machine() == 'arm64' else 0) + tol=0 if platform.machine() == 'x86_64' else 0.018) def test_given_colors_levels_and_extends(): # Remove this line when this test image is regenerated. plt.rcParams['pcolormesh.snap'] = False @@ -171,8 +172,7 @@ def test_given_colors_levels_and_extends(): plt.colorbar(c, ax=ax) -@image_comparison(['contourf_hatch_colors'], - remove_text=True, style='mpl20', extensions=['png']) +@image_comparison(['contourf_hatch_colors.png'], remove_text=True, style='mpl20') def test_hatch_colors(): fig, ax = plt.subplots() cf = ax.contourf([[0, 1], [1, 2]], hatches=['-', '/', '\\', '//'], cmap='gray') @@ -214,7 +214,23 @@ def test_log_locator_levels(): assert_array_almost_equal(cb.ax.get_yticks(), c.levels) -@image_comparison(['contour_datetime_axis.png'], style='mpl20') +@pytest.mark.parametrize("n_levels", [2, 3, 4, 5, 6]) +def test_lognorm_levels(n_levels): + x, y = np.mgrid[1:10:0.1, 1:10:0.1] + data = np.abs(np.sin(x)*np.exp(y)) + + fig, ax = plt.subplots() + im = ax.contour(x, y, data, norm=LogNorm(), levels=n_levels) + fig.colorbar(im, ax=ax) + + levels = im.levels + visible_levels = levels[(levels <= data.max()) & (levels >= data.min())] + # levels parameter promises "no more than n+1 "nice" contour levels " + assert len(visible_levels) <= n_levels + 1 + + +# TODO: tighten tolerance after baseline image is regenerated for text overhaul +@image_comparison(['contour_datetime_axis.png'], style='mpl20', tol=0.3) def test_contour_datetime_axis(): fig = plt.figure() fig.subplots_adjust(hspace=0.4, top=0.98, bottom=.15) @@ -399,8 +415,11 @@ def test_contourf_log_extension(): levels = np.power(10., levels_exp) # original data + # FIXME: Force tick locations for now for backcompat with old test + # (log-colorbar extension is not really optimal anyways). c1 = ax1.contourf(data, - norm=LogNorm(vmin=data.min(), vmax=data.max())) + norm=LogNorm(vmin=data.min(), vmax=data.max()), + locator=mpl.ticker.FixedLocator(10.**np.arange(-8, 12, 2))) # just show data in levels c2 = ax2.contourf(data, levels=levels, norm=LogNorm(vmin=levels.min(), vmax=levels.max()), @@ -416,10 +435,8 @@ def test_contourf_log_extension(): cb = plt.colorbar(c3, ax=ax3) -@image_comparison( - ['contour_addlines.png'], remove_text=True, style='mpl20', - tol=0.15 if platform.machine() in ('aarch64', 'arm64', 'ppc64le', 's390x') - else 0.03) +@image_comparison(['contour_addlines.png'], remove_text=True, style='mpl20', + tol=0.03 if platform.machine() == 'x86_64' else 0.15) # tolerance is because image changed minutely when tick finding on # colorbars was cleaned up... def test_contour_addlines(): @@ -437,8 +454,7 @@ def test_contour_addlines(): assert_array_almost_equal(cb.ax.get_ylim(), [114.3091, 9972.30735], 3) -@image_comparison(baseline_images=['contour_uneven'], - extensions=['png'], remove_text=True, style='mpl20') +@image_comparison(['contour_uneven.png'], remove_text=True, style='mpl20') def test_contour_uneven(): # Remove this line when this test image is regenerated. plt.rcParams['pcolormesh.snap'] = False @@ -476,8 +492,7 @@ def test_label_nonagg(): plt.clabel(plt.contour([[1, 2], [3, 4]])) -@image_comparison(baseline_images=['contour_closed_line_loop'], - extensions=['png'], remove_text=True) +@image_comparison(['contour_closed_line_loop.png'], remove_text=True) def test_contour_closed_line_loop(): # github issue 19568. z = [[0, 0, 0], [0, 2, 0], [0, 0, 0], [2, 1, 2]] @@ -501,8 +516,7 @@ def test_quadcontourset_reuse(): assert qcs3._contour_generator == qcs1._contour_generator -@image_comparison(baseline_images=['contour_manual'], - extensions=['png'], remove_text=True, tol=0.89) +@image_comparison(['contour_manual.png'], remove_text=True, tol=0.89) def test_contour_manual(): # Manually specifying contour lines/polygons to plot. from matplotlib.contour import ContourSet @@ -527,8 +541,7 @@ def test_contour_manual(): ContourSet(ax, [2], [segs], [kinds], colors='k', linewidths=3) -@image_comparison(baseline_images=['contour_line_start_on_corner_edge'], - extensions=['png'], remove_text=True) +@image_comparison(['contour_line_start_on_corner_edge.png'], remove_text=True) def test_contour_line_start_on_corner_edge(): fig, ax = plt.subplots(figsize=(6, 5)) @@ -600,8 +613,7 @@ def test_contourf_legend_elements(): cs = plt.contourf(h, levels=[10, 30, 50], colors=['#FFFF00', '#FF00FF', '#00FFFF'], extend='both') - cs.cmap.set_over('red') - cs.cmap.set_under('blue') + cs.cmap = cs.cmap.with_extremes(over='red', under='blue') cs.changed() artists, labels = cs.legend_elements() assert labels == ['$x \\leq -1e+250s$', @@ -663,8 +675,7 @@ def test_algorithm_supports_corner_mask(algorithm): plt.contourf(z, algorithm=algorithm, corner_mask=True) -@image_comparison(baseline_images=['contour_all_algorithms'], - extensions=['png'], remove_text=True, tol=0.06) +@image_comparison(['contour_all_algorithms.png'], remove_text=True, tol=0.06) def test_all_algorithms(): algorithms = ['mpl2005', 'mpl2014', 'serial', 'threaded'] @@ -839,3 +850,25 @@ def test_allsegs_allkinds(): assert len(result) == 2 assert len(result[0]) == 5 assert len(result[1]) == 4 + + +@image_comparison(['contour_rasterization.pdf'], savefig_kwarg={'dpi': 25}, + style='mpl20') +def test_contourf_rasterize(): + fig, ax = plt.subplots() + data = [[0, 1], [1, 0]] + circle = mpatches.Circle([0.5, 0.5], 0.5, transform=ax.transAxes) + cs = ax.contourf(data, clip_path=circle, rasterized=True) + assert cs._rasterized + + +@check_figures_equal() +def test_contour_aliases(fig_test, fig_ref): + data = np.arange(100).reshape((10, 10)) ** 2 + fig_test.add_subplot().contour(data, linestyle=":") + fig_ref.add_subplot().contour(data, linestyles="dotted") + + +def test_contour_singular_color(): + with pytest.raises(TypeError): + plt.figure().add_subplot().contour([[0, 1], [2, 3]], color="r") diff --git a/lib/matplotlib/tests/test_dates.py b/lib/matplotlib/tests/test_dates.py index 2d60e3525b2a..d3f64d73002e 100644 --- a/lib/matplotlib/tests/test_dates.py +++ b/lib/matplotlib/tests/test_dates.py @@ -152,7 +152,8 @@ def test_date_axhspan(): fig.subplots_adjust(left=0.25) -@image_comparison(['date_axvspan.png']) +# TODO: tighten tolerance after baseline image is regenerated for text overhaul +@image_comparison(['date_axvspan.png'], tol=0.07) def test_date_axvspan(): # test axvspan with date inputs t0 = datetime.datetime(2000, 1, 20) @@ -176,7 +177,8 @@ def test_date_axhline(): fig.subplots_adjust(left=0.25) -@image_comparison(['date_axvline.png']) +# TODO: tighten tolerance after baseline image is regenerated for text overhaul +@image_comparison(['date_axvline.png'], tol=0.09) def test_date_axvline(): # test axvline with date inputs t0 = datetime.datetime(2000, 1, 20) @@ -199,7 +201,7 @@ def test_too_many_date_ticks(caplog): tf = datetime.datetime(2000, 1, 20) fig, ax = plt.subplots() with pytest.warns(UserWarning) as rec: - ax.set_xlim((t0, tf), auto=True) + ax.set_xlim(t0, tf, auto=True) assert len(rec) == 1 assert ('Attempting to set identical low and high xlims' in str(rec[0].message)) @@ -226,7 +228,8 @@ def wrapper(): return wrapper -@image_comparison(['RRuleLocator_bounds.png']) +# TODO: tighten tolerance after baseline image is regenerated for text overhaul +@image_comparison(['RRuleLocator_bounds.png'], tol=0.07) def test_RRuleLocator(): import matplotlib.testing.jpl_units as units units.register() @@ -270,12 +273,13 @@ def test_RRuleLocator_close_minmax(): assert list(map(str, mdates.num2date(loc.tick_values(d1, d2)))) == expected -@image_comparison(['DateFormatter_fractionalSeconds.png']) +# TODO: tighten tolerance after baseline image is regenerated for text overhaul +@image_comparison(['DateFormatter_fractionalSeconds.png'], tol=0.11) def test_DateFormatter(): import matplotlib.testing.jpl_units as units units.register() - # Lets make sure that DateFormatter will allow us to have tick marks + # Let's make sure that DateFormatter will allow us to have tick marks # at intervals of fractional seconds. t0 = datetime.datetime(2001, 1, 1, 0, 0, 0) @@ -373,7 +377,7 @@ def test_drange(): end = datetime.datetime(2011, 1, 2, tzinfo=mdates.UTC) delta = datetime.timedelta(hours=1) # We expect 24 values in drange(start, end, delta), because drange returns - # dates from an half open interval [start, end) + # dates from a half open interval [start, end) assert len(mdates.drange(start, end, delta)) == 24 # Same if interval ends slightly earlier @@ -668,10 +672,12 @@ def test_concise_converter_stays(): fig, ax = plt.subplots() ax.plot(x, y) # Bypass Switchable date converter - ax.xaxis.converter = conv = mdates.ConciseDateConverter() + conv = mdates.ConciseDateConverter() + with pytest.warns(UserWarning, match="already has a converter"): + ax.xaxis.set_converter(conv) assert ax.xaxis.units is None ax.set_xlim(*x) - assert ax.xaxis.converter == conv + assert ax.xaxis.get_converter() == conv def test_offset_changes(): diff --git a/lib/matplotlib/tests/test_datetime.py b/lib/matplotlib/tests/test_datetime.py index 276056d044ae..c246fb23f011 100644 --- a/lib/matplotlib/tests/test_datetime.py +++ b/lib/matplotlib/tests/test_datetime.py @@ -259,11 +259,22 @@ def test_bxp(self): ax.xaxis.set_major_formatter(mpl.dates.DateFormatter("%Y-%m-%d")) ax.set_title('Box plot with datetime data') - @pytest.mark.xfail(reason="Test for clabel not written yet") @mpl.style.context("default") def test_clabel(self): + dates = [datetime.datetime(2023, 10, 1) + datetime.timedelta(days=i) + for i in range(10)] + x = np.arange(-10.0, 5.0, 0.5) + X, Y = np.meshgrid(x, dates) + Z = np.arange(X.size).reshape(X.shape) + fig, ax = plt.subplots() - ax.clabel(...) + CS = ax.contour(X, Y, Z) + labels = ax.clabel(CS, manual=[(x[0], dates[0])]) + assert len(labels) == 1 + assert labels[0].get_text() == '0' + x_pos, y_pos = labels[0].get_position() + assert x_pos == pytest.approx(-10.0, abs=1e-3) + assert y_pos == pytest.approx(mpl.dates.date2num(dates[0]), abs=1e-3) @mpl.style.context("default") def test_contour(self): @@ -642,26 +653,6 @@ def test_plot(self): ax2.plot(range(1, N), x) ax3.plot(x, x) - @mpl.style.context("default") - def test_plot_date(self): - mpl.rcParams["date.converter"] = "concise" - range_threshold = 10 - fig, (ax1, ax2, ax3) = plt.subplots(3, 1, layout="constrained") - - x_dates = np.array( - [datetime.datetime(2023, 10, delta) for delta in range(1, range_threshold)] - ) - y_dates = np.array( - [datetime.datetime(2023, 10, delta) for delta in range(1, range_threshold)] - ) - x_ranges = np.array(range(1, range_threshold)) - y_ranges = np.array(range(1, range_threshold)) - - with pytest.warns(mpl.MatplotlibDeprecationWarning): - ax1.plot_date(x_dates, y_dates) - ax2.plot_date(x_dates, y_ranges) - ax3.plot_date(x_ranges, y_dates) - @pytest.mark.xfail(reason="Test for quiver not written yet") @mpl.style.context("default") def test_quiver(self): @@ -830,11 +821,32 @@ def test_triplot(self): fig, ax = plt.subplots() ax.triplot(...) - @pytest.mark.xfail(reason="Test for violin not written yet") + @pytest.mark.parametrize("orientation", ["vertical", "horizontal"]) @mpl.style.context("default") - def test_violin(self): + def test_violin(self, orientation): fig, ax = plt.subplots() - ax.violin(...) + datetimes = [ + datetime.datetime(2023, 2, 10), + datetime.datetime(2023, 5, 18), + datetime.datetime(2023, 6, 6) + ] + ax.violin( + [ + { + 'coords': datetimes, + 'vals': [0.1, 0.5, 0.2], + 'mean': datetimes[1], + 'median': datetimes[1], + 'min': datetimes[0], + 'max': datetimes[-1], + 'quantiles': datetimes + } + ], + orientation=orientation, + # TODO: It should be possible for positions to be datetimes too + # https://github.com/matplotlib/matplotlib/issues/30417 + # positions=[datetime.datetime(2020, 1, 1)] + ) @pytest.mark.xfail(reason="Test for violinplot not written yet") @mpl.style.context("default") diff --git a/lib/matplotlib/tests/test_determinism.py b/lib/matplotlib/tests/test_determinism.py index 2ecc40dbd3c0..c0e4adbef40b 100644 --- a/lib/matplotlib/tests/test_determinism.py +++ b/lib/matplotlib/tests/test_determinism.py @@ -26,21 +26,19 @@ def _save_figure(objects='mhip', fmt="pdf", usetex=False): mpl.use(fmt) mpl.rcParams.update({'svg.hashsalt': 'asdf', 'text.usetex': usetex}) - fig = plt.figure() - - if 'm' in objects: + def plot_markers(fig): # use different markers... - ax1 = fig.add_subplot(1, 6, 1) + ax = fig.add_subplot() x = range(10) - ax1.plot(x, [1] * 10, marker='D') - ax1.plot(x, [2] * 10, marker='x') - ax1.plot(x, [3] * 10, marker='^') - ax1.plot(x, [4] * 10, marker='H') - ax1.plot(x, [5] * 10, marker='v') + ax.plot(x, [1] * 10, marker='D') + ax.plot(x, [2] * 10, marker='x') + ax.plot(x, [3] * 10, marker='^') + ax.plot(x, [4] * 10, marker='H') + ax.plot(x, [5] * 10, marker='v') - if 'h' in objects: + def plot_hatch(fig): # also use different hatch patterns - ax2 = fig.add_subplot(1, 6, 2) + ax2 = fig.add_subplot() bars = (ax2.bar(range(1, 5), range(1, 5)) + ax2.bar(range(1, 5), [6] * 4, bottom=range(1, 5))) ax2.set_xticks([1.5, 2.5, 3.5, 4.5]) @@ -49,17 +47,17 @@ def _save_figure(objects='mhip', fmt="pdf", usetex=False): for bar, pattern in zip(bars, patterns): bar.set_hatch(pattern) - if 'i' in objects: + def plot_image(fig): + axs = fig.subplots(1, 3, sharex=True, sharey=True) # also use different images A = [[1, 2, 3], [2, 3, 1], [3, 1, 2]] - fig.add_subplot(1, 6, 3).imshow(A, interpolation='nearest') + axs[0].imshow(A, interpolation='nearest') A = [[1, 3, 2], [1, 2, 3], [3, 1, 2]] - fig.add_subplot(1, 6, 4).imshow(A, interpolation='bilinear') + axs[1].imshow(A, interpolation='bilinear') A = [[2, 3, 1], [1, 2, 3], [2, 1, 3]] - fig.add_subplot(1, 6, 5).imshow(A, interpolation='bicubic') - - if 'p' in objects: + axs[2].imshow(A, interpolation='bicubic') + def plot_paths(fig): # clipping support class, copied from demo_text_path.py gallery example class PathClippedImagePatch(PathPatch): """ @@ -85,13 +83,15 @@ def draw(self, renderer=None): self.bbox_image.draw(renderer) super().draw(renderer) + subfigs = fig.subfigures(1, 3) + # add a polar projection - px = fig.add_subplot(projection="polar") + px = subfigs[0].add_subplot(projection="polar") pimg = px.imshow([[2]]) pimg.set_clip_path(Circle((0, 1), radius=0.3333)) # add a text-based clipping path (origin: demo_text_path.py) - (ax1, ax2) = fig.subplots(2) + ax = subfigs[1].add_subplot() arr = plt.imread(get_sample_data("grace_hopper.jpg")) text_path = TextPath((0, 0), "!?", size=150) p = PathClippedImagePatch(text_path, arr, ec="k") @@ -99,7 +99,7 @@ def draw(self, renderer=None): offsetbox.add_artist(p) ao = AnchoredOffsetbox(loc='upper left', child=offsetbox, frameon=True, borderpad=0.2) - ax1.add_artist(ao) + ax.add_artist(ao) # add a 2x2 grid of path-clipped axes (origin: test_artist.py) exterior = Path.unit_rectangle().deepcopy() @@ -112,7 +112,8 @@ def draw(self, renderer=None): star = Path.unit_regular_star(6).deepcopy() star.vertices *= 2.6 - (row1, row2) = fig.subplots(2, 2, sharex=True, sharey=True) + (row1, row2) = subfigs[2].subplots(2, 2, sharex=True, sharey=True, + gridspec_kw=dict(hspace=0, wspace=0)) for row in (row1, row2): ax1, ax2 = row collection = PathCollection([star], lw=5, edgecolor='blue', @@ -128,8 +129,22 @@ def draw(self, renderer=None): ax1.set_xlim([-3, 3]) ax1.set_ylim([-3, 3]) + nfigs = len(objects) + 1 + fig = plt.figure(figsize=(7, 3 * nfigs)) + subfigs = iter(fig.subfigures(nfigs, squeeze=False).flat) + fig.subplots_adjust(bottom=0.15) + + if 'm' in objects: + plot_markers(next(subfigs)) + if 'h' in objects: + plot_hatch(next(subfigs)) + if 'i' in objects: + plot_image(next(subfigs)) + if 'p' in objects: + plot_paths(next(subfigs)) + x = range(5) - ax = fig.add_subplot(1, 6, 6) + ax = next(subfigs).add_subplot() ax.plot(x, x) ax.set_title('A string $1+2+\\sigma$') ax.set_xlabel('A string $1+2+\\sigma$') @@ -147,8 +162,7 @@ def draw(self, renderer=None): ("i", "pdf", False), ("mhip", "pdf", False), ("mhip", "ps", False), - pytest.param( - "mhip", "ps", True, marks=[needs_usetex, needs_ghostscript]), + pytest.param("mhip", "ps", True, marks=[needs_usetex, needs_ghostscript]), ("p", "svg", False), ("mhip", "svg", False), pytest.param("mhip", "svg", True, marks=needs_usetex), @@ -156,8 +170,7 @@ def draw(self, renderer=None): ) def test_determinism_check(objects, fmt, usetex): """ - Output three times the same graphs and checks that the outputs are exactly - the same. + Output the same graph three times and check that the outputs are exactly the same. Parameters ---------- @@ -197,10 +210,11 @@ def test_determinism_check(objects, fmt, usetex): ) def test_determinism_source_date_epoch(fmt, string): """ - Test SOURCE_DATE_EPOCH support. Output a document with the environment - variable SOURCE_DATE_EPOCH set to 2000-01-01 00:00 UTC and check that the - document contains the timestamp that corresponds to this date (given as an - argument). + Test SOURCE_DATE_EPOCH support. + + Output a document with the environment variable SOURCE_DATE_EPOCH set to + 2000-01-01 00:00 UTC and check that the document contains the timestamp that + corresponds to this date (given as an argument). Parameters ---------- diff --git a/lib/matplotlib/tests/test_doc.py b/lib/matplotlib/tests/test_doc.py index 3e28fd1b8eb7..f3d6d6e3fd5d 100644 --- a/lib/matplotlib/tests/test_doc.py +++ b/lib/matplotlib/tests/test_doc.py @@ -7,9 +7,9 @@ def test_sphinx_gallery_example_header(): This test monitors that the version we have copied is still the same as the EXAMPLE_HEADER in sphinx-gallery. If sphinx-gallery changes its EXAMPLE_HEADER, this test will start to fail. In that case, please update - the monkey-patching of EXAMPLE_HEADER in conf.py. + the monkey-patching of EXAMPLE_HEADER in sphinxext/util.py. """ - pytest.importorskip('sphinx_gallery', minversion='0.16.0') + pytest.importorskip('sphinx_gallery', minversion='0.20.0') from sphinx_gallery import gen_rst EXAMPLE_HEADER = """ @@ -25,7 +25,7 @@ def test_sphinx_gallery_example_header(): :class: sphx-glr-download-link-note :ref:`Go to the end ` - to download the full example code.{2} + to download the full example code{2} .. rst-class:: sphx-glr-example-title diff --git a/lib/matplotlib/tests/test_dviread.py b/lib/matplotlib/tests/test_dviread.py index 7b7ff151be18..3581a97c1390 100644 --- a/lib/matplotlib/tests/test_dviread.py +++ b/lib/matplotlib/tests/test_dviread.py @@ -1,8 +1,10 @@ import json from pathlib import Path import shutil +import sys -import matplotlib.dviread as dr +from matplotlib import cbook, dviread as dr +from matplotlib.testing import subprocess_run_for_testing, _has_tex_package import pytest @@ -60,18 +62,88 @@ def test_PsfontsMap(monkeypatch): fontmap[b'%'] -@pytest.mark.skipif(shutil.which("kpsewhich") is None, +@pytest.mark.skipif(sys.platform == "emscripten" or shutil.which("kpsewhich") is None, reason="kpsewhich is not available") -def test_dviread(): - dirpath = Path(__file__).parent / 'baseline_images/dviread' - with (dirpath / 'test.json').open() as f: - correct = json.load(f) - with dr.Dvi(str(dirpath / 'test.dvi'), None) as dvi: - data = [{'text': [[t.x, t.y, - chr(t.glyph), - t.font.texname.decode('ascii'), - round(t.font.size, 2)] - for t in page.text], - 'boxes': [[b.x, b.y, b.height, b.width] for b in page.boxes]} - for page in dvi] +@pytest.mark.parametrize("engine", ["pdflatex", "xelatex", "lualatex"]) +def test_dviread(tmp_path, engine, monkeypatch): + dirpath = Path(__file__).parent / "baseline_images/dviread" + shutil.copy(dirpath / "test.tex", tmp_path) + shutil.copy(cbook._get_data_path("fonts/ttf/DejaVuSans.ttf"), tmp_path) + cmd, fmt = { + "pdflatex": (["latex", "-no-shell-escape"], "dvi"), + "xelatex": (["xelatex", "-no-pdf", "-no-shell-escape"], "xdv"), + "lualatex": (["lualatex", "-output-format=dvi", "-no-shell-escape"], "dvi"), + }[engine] + if shutil.which(cmd[0]) is None: + pytest.skip(f"{cmd[0]} is not available") + subprocess_run_for_testing( + [*cmd, "test.tex"], cwd=tmp_path, check=True, capture_output=True) + # dviread must be run from the tmppath directory because {xe,lua}tex output + # records the path to DejaVuSans.ttf as it is written in the tex source, + # i.e. as a relative path. + monkeypatch.chdir(tmp_path) + with dr.Dvi(tmp_path / f"test.{fmt}", None) as dvi: + try: + pages = [*dvi] + except FileNotFoundError as exc: + for note in getattr(exc, "__notes__", []): + if "too-old version of luaotfload" in note: + pytest.skip(note) + raise + data = [ + { + "text": [ + [ + t.x, t.y, + t._as_unicode_or_name(), + t.font.resolve_path().name, + round(t.font.size, 2), + t.font.effects, + ] for t in page.text + ], + "boxes": [[b.x, b.y, b.height, b.width] for b in page.boxes] + } for page in pages + ] + correct = json.loads((dirpath / f"{engine}.json").read_text()) + assert data == correct + + +@pytest.mark.skipif(shutil.which("latex") is None, reason="latex is not available") +@pytest.mark.skipif(not _has_tex_package("concmath"), reason="needs concmath.sty") +def test_dviread_pk(tmp_path): + (tmp_path / "test.tex").write_text(r""" + \documentclass{article} + \usepackage{concmath} + \pagestyle{empty} + \begin{document} + Hi! + \end{document} + """) + subprocess_run_for_testing( + ["latex", "-no-shell-escape", "test.tex"], + cwd=tmp_path, check=True, capture_output=True) + with dr.Dvi(tmp_path / "test.dvi", None) as dvi: + pages = [*dvi] + data = [ + { + "text": [ + [ + t.x, t.y, + t._as_unicode_or_name(), + t.font.resolve_path().name, + round(t.font.size, 2), + t.font.effects, + ] for t in page.text + ], + "boxes": [[b.x, b.y, b.height, b.width] for b in page.boxes] + } for page in pages + ] + correct = [{ + 'boxes': [], + 'text': [ + [5046272, 4128768, 'H?', 'ccr10.600pk', 9.96, {}], + [5530510, 4128768, 'i?', 'ccr10.600pk', 9.96, {}], + [5716195, 4128768, '!?', 'ccr10.600pk', 9.96, {}], + ], + }] assert data == correct diff --git a/lib/matplotlib/tests/test_figure.py b/lib/matplotlib/tests/test_figure.py index 61fd71875e2a..27adf980278c 100644 --- a/lib/matplotlib/tests/test_figure.py +++ b/lib/matplotlib/tests/test_figure.py @@ -3,6 +3,7 @@ import io import pickle import platform +import sys from threading import Timer from types import SimpleNamespace import warnings @@ -25,8 +26,9 @@ import matplotlib.dates as mdates +# TODO: tighten tolerance after baseline image is regenerated for text overhaul @image_comparison(['figure_align_labels'], extensions=['png', 'svg'], - tol=0 if platform.machine() == 'x86_64' else 0.01) + tol=0.1 if platform.machine() == 'x86_64' else 0.1) def test_align_labels(): fig = plt.figure(layout='tight') gs = gridspec.GridSpec(3, 3) @@ -66,9 +68,10 @@ def test_align_labels(): fig.align_labels() +# TODO: tighten tolerance after baseline image is regenerated for text overhaul @image_comparison(['figure_align_titles_tight.png', 'figure_align_titles_constrained.png'], - tol=0 if platform.machine() == 'x86_64' else 0.022, + tol=0.3 if platform.machine() == 'x86_64' else 0.04, style='mpl20') def test_align_titles(): for layout in ['tight', 'constrained']: @@ -147,8 +150,6 @@ def test_figure_label(): assert plt.get_figlabels() == ['', 'today'] plt.figure(fig_today) assert plt.gcf() == fig_today - with pytest.raises(ValueError): - plt.figure(Figure()) def test_figure_label_replaced(): @@ -209,8 +210,8 @@ def test_clf_keyword(): assert [t.get_text() for t in fig2.texts] == [] -@image_comparison(['figure_today'], - tol=0.015 if platform.machine() == 'arm64' else 0) +@image_comparison(['figure_today.png'], + tol=0 if platform.machine() == 'x86_64' else 0.015) def test_figure(): # named figure support fig = plt.figure('today') @@ -225,7 +226,7 @@ def test_figure(): plt.close('tomorrow') -@image_comparison(['figure_legend']) +@image_comparison(['figure_legend.png']) def test_figure_legend(): fig, axs = plt.subplots(2) axs[0].plot([0, 1], [1, 0], label='x', color='g') @@ -241,7 +242,7 @@ def test_gca(): fig = plt.figure() # test that gca() picks up Axes created via add_axes() - ax0 = fig.add_axes([0, 0, 1, 1]) + ax0 = fig.add_axes((0, 0, 1, 1)) assert fig.gca() is ax0 # test that gca() picks up Axes created via add_subplot() @@ -322,7 +323,8 @@ def test_add_subplot_invalid(): fig.add_subplot(ax) -@image_comparison(['figure_suptitle']) +# TODO: tighten tolerance after baseline image is regenerated for text overhaul +@image_comparison(['figure_suptitle.png'], tol=0.02) def test_suptitle(): fig, _ = plt.subplots() fig.suptitle('hello', color='r') @@ -364,6 +366,28 @@ def test_get_suptitle_supxlabel_supylabel(): assert fig.get_supylabel() == 'supylabel' +def test_remove_suptitle_supxlabel_supylabel(): + fig = plt.figure() + + title = fig.suptitle('suptitle') + xlabel = fig.supxlabel('supxlabel') + ylabel = fig.supylabel('supylabel') + + assert len(fig.texts) == 3 + assert fig._suptitle is not None + assert fig._supxlabel is not None + assert fig._supylabel is not None + + title.remove() + assert fig._suptitle is None + xlabel.remove() + assert fig._supxlabel is None + ylabel.remove() + assert fig._supylabel is None + + assert not fig.texts + + @image_comparison(['alpha_background'], # only test png and svg. The PDF output appears correct, # but Ghostscript does not preserve the background color. @@ -496,6 +520,20 @@ def test_autofmt_xdate(which): assert int(label.get_rotation()) == angle +def test_autofmt_xdate_colorbar_constrained(): + # check works with a colorbar. + # with constrained layout, colorbars do not have a gridspec, + # but autofmt_xdate checks if all axes have a gridspec before being + # applied. + fig, ax = plt.subplots(layout="constrained") + im = ax.imshow([[1, 4, 6], [2, 3, 5]]) + plt.colorbar(im) + fig.autofmt_xdate() + fig.draw_without_rendering() + label = ax.get_xticklabels(which='major')[1] + assert label.get_rotation() == 30.0 + + @mpl.style.context('default') def test_change_dpi(): fig = plt.figure(figsize=(4, 4)) @@ -532,7 +570,7 @@ def test_invalid_figure_add_axes(): fig.add_axes((.1, .1, .5, np.nan)) with pytest.raises(TypeError, match="multiple values for argument 'rect'"): - fig.add_axes([0, 0, 1, 1], rect=[0, 0, 1, 1]) + fig.add_axes((0, 0, 1, 1), rect=[0, 0, 1, 1]) fig2, ax = plt.subplots() with pytest.raises(ValueError, @@ -545,7 +583,7 @@ def test_invalid_figure_add_axes(): fig2.add_axes(ax, "extra positional argument") with pytest.raises(TypeError, match=r"add_axes\(\) takes 1 positional arguments"): - fig.add_axes([0, 0, 1, 1], "extra positional argument") + fig.add_axes((0, 0, 1, 1), "extra positional argument") def test_subplots_shareax_loglabels(): @@ -635,7 +673,7 @@ def test_savefig_locate_colorbar(): @mpl.rc_context({"savefig.transparent": True}) -@check_figures_equal(extensions=["png"]) +@check_figures_equal() def test_savefig_transparent(fig_test, fig_ref): # create two transparent subfigures with corresponding transparent inset # axes. the entire background of the image should be transparent. @@ -728,7 +766,7 @@ def test_invalid_layouts(): fig.set_layout_engine("constrained") -@check_figures_equal(extensions=["png"]) +@check_figures_equal() def test_tightlayout_autolayout_deconflict(fig_test, fig_ref): for fig, autolayout in zip([fig_ref, fig_test], [False, True]): with mpl.rc_context({'figure.autolayout': autolayout}): @@ -988,7 +1026,7 @@ def test_animated_with_canvas_change(fig_test, fig_ref): class TestSubplotMosaic: - @check_figures_equal(extensions=["png"]) + @check_figures_equal() @pytest.mark.parametrize( "x", [ [["A", "A", "B"], ["C", "D", "B"]], @@ -1020,7 +1058,7 @@ def test_basic(self, fig_test, fig_ref, x): axD = fig_ref.add_subplot(gs[1, 1]) axD.set_title(labels[3]) - @check_figures_equal(extensions=["png"]) + @check_figures_equal() def test_all_nested(self, fig_test, fig_ref): x = [["A", "B"], ["C", "D"]] y = [["E", "F"], ["G", "H"]] @@ -1043,7 +1081,7 @@ def test_all_nested(self, fig_test, fig_ref): for k, label in enumerate(r): fig_ref.add_subplot(gs_right[j, k]).set_title(label) - @check_figures_equal(extensions=["png"]) + @check_figures_equal() def test_nested(self, fig_test, fig_ref): fig_ref.set_layout_engine("constrained") @@ -1077,7 +1115,7 @@ def test_nested(self, fig_test, fig_ref): axF = fig_ref.add_subplot(gs[0, 0]) axF.set_title("F") - @check_figures_equal(extensions=["png"]) + @check_figures_equal() def test_nested_tuple(self, fig_test, fig_ref): x = [["A", "B", "B"], ["C", "C", "D"]] xt = (("A", "B", "B"), ("C", "C", "D")) @@ -1105,7 +1143,7 @@ def test_nested_height_ratios(self): assert axd["D"].get_gridspec().get_height_ratios() == height_ratios assert axd["B"].get_gridspec().get_height_ratios() != height_ratios - @check_figures_equal(extensions=["png"]) + @check_figures_equal() @pytest.mark.parametrize( "x, empty_sentinel", [ @@ -1150,7 +1188,7 @@ def test_fail_list_of_str(self): with pytest.raises(ValueError, match='must be 2D'): plt.subplot_mosaic([['a', 'b'], [('a', 'b'), 'c']]) - @check_figures_equal(extensions=["png"]) + @check_figures_equal() @pytest.mark.parametrize("subplot_kw", [{}, {"projection": "polar"}, None]) def test_subplot_kw(self, fig_test, fig_ref, subplot_kw): x = [[1, 2]] @@ -1162,7 +1200,7 @@ def test_subplot_kw(self, fig_test, fig_ref, subplot_kw): axB = fig_ref.add_subplot(gs[0, 1], **subplot_kw) - @check_figures_equal(extensions=["png"]) + @check_figures_equal() @pytest.mark.parametrize("multi_value", ['BC', tuple('BC')]) def test_per_subplot_kw(self, fig_test, fig_ref, multi_value): x = 'AB;CD' @@ -1217,7 +1255,7 @@ def test_extra_per_subplot_kw(self): ): Figure().subplot_mosaic("A", per_subplot_kw={"B": {}}) - @check_figures_equal(extensions=["png"]) + @check_figures_equal() @pytest.mark.parametrize("str_pattern", ["AAA\nBBB", "\nAAA\nBBB\n", "ABC\nDEF"] ) @@ -1254,7 +1292,7 @@ def test_fail(self, x, match): with pytest.raises(ValueError, match=match): fig.subplot_mosaic(x) - @check_figures_equal(extensions=["png"]) + @check_figures_equal() def test_hashable_keys(self, fig_test, fig_ref): fig_test.subplot_mosaic([[object(), object()]]) fig_ref.subplot_mosaic([["A", "B"]]) @@ -1384,8 +1422,9 @@ def test_subfigure_ss(): fig.suptitle('Figure suptitle', fontsize='xx-large') +# TODO: tighten tolerance after baseline image is regenerated for text overhaul @image_comparison(['test_subfigure_double.png'], style='mpl20', - savefig_kwarg={'facecolor': 'teal'}) + savefig_kwarg={'facecolor': 'teal'}, tol=0.02) def test_subfigure_double(): # test assigning the subfigure via subplotspec np.random.seed(19680801) @@ -1569,28 +1608,30 @@ def test_add_subplot_kwargs(): def test_add_axes_kwargs(): # fig.add_axes() always creates new axes, even if axes kwargs differ. fig = plt.figure() - ax = fig.add_axes([0, 0, 1, 1]) - ax1 = fig.add_axes([0, 0, 1, 1]) + ax = fig.add_axes((0, 0, 1, 1)) + ax1 = fig.add_axes((0, 0, 1, 1)) assert ax is not None assert ax1 is not ax plt.close() fig = plt.figure() - ax = fig.add_axes([0, 0, 1, 1], projection='polar') - ax1 = fig.add_axes([0, 0, 1, 1], projection='polar') + ax = fig.add_axes((0, 0, 1, 1), projection='polar') + ax1 = fig.add_axes((0, 0, 1, 1), projection='polar') assert ax is not None assert ax1 is not ax plt.close() fig = plt.figure() - ax = fig.add_axes([0, 0, 1, 1], projection='polar') - ax1 = fig.add_axes([0, 0, 1, 1]) + ax = fig.add_axes((0, 0, 1, 1), projection='polar') + ax1 = fig.add_axes((0, 0, 1, 1)) assert ax is not None assert ax1.name == 'rectilinear' assert ax1 is not ax plt.close() +@pytest.mark.skipif(sys.platform == 'emscripten', + reason='emscripten does not support threads') def test_ginput(recwarn): # recwarn undoes warn filters at exit. warnings.filterwarnings("ignore", "cannot show the figure") fig, ax = plt.subplots() @@ -1613,6 +1654,8 @@ def multi_presses(): np.testing.assert_allclose(fig.ginput(3), [(.3, .4), (.5, .6)]) +@pytest.mark.skipif(sys.platform == 'emscripten', + reason='emscripten does not support threads') def test_waitforbuttonpress(recwarn): # recwarn undoes warn filters at exit. warnings.filterwarnings("ignore", "cannot show the figure") fig = plt.figure() @@ -1631,7 +1674,7 @@ def test_kwargs_pass(): assert sub_fig.get_label() == 'sub figure' -@check_figures_equal(extensions=["png"]) +@check_figures_equal() def test_rcparams(fig_test, fig_ref): fig_ref.supxlabel("xlabel", weight='bold', size=15) fig_ref.supylabel("ylabel", weight='bold', size=15) @@ -1676,6 +1719,9 @@ def test_unpickle_with_device_pixel_ratio(): assert fig.dpi == 42*7 fig2 = pickle.loads(pickle.dumps(fig)) assert fig2.dpi == 42 + assert all( + [orig / 7 == restore for orig, restore in zip(fig.bbox.max, fig2.bbox.max)] + ) def test_gridspec_no_mutate_input(): @@ -1756,6 +1802,24 @@ def test_warn_colorbar_mismatch(): subfig3_1.colorbar(im4_1) +def test_clf_subplotpars(): + keys = ('left', 'right', 'bottom', 'top', 'wspace', 'hspace') + rc_params = {key: plt.rcParams['figure.subplot.' + key] for key in keys} + + fig = plt.figure(1) + fig.subplots_adjust(**{k: v+0.01 for k, v in rc_params.items()}) + fig.clf() + assert fig.subplotpars.to_dict() == rc_params + + +def test_suplots_adjust_incremental(): + fig = plt.figure() + fig.subplots_adjust(left=0) + fig.subplots_adjust(right=1) + assert fig.subplotpars.left == 0 + assert fig.subplotpars.right == 1 + + def test_set_figure(): fig = plt.figure() sfig1 = fig.subfigures() @@ -1805,3 +1869,19 @@ def test_subfigure_stale_propagation(): sfig2.stale = True assert sfig1.stale assert fig.stale + + +@pytest.mark.parametrize("figsize, figsize_inches", [ + ((6, 4), (6, 4)), + ((6, 4, "in"), (6, 4)), + ((5.08, 2.54, "cm"), (2, 1)), + ((600, 400, "px"), (6, 4)), +]) +def test_figsize(figsize, figsize_inches): + fig = plt.figure(figsize=figsize, dpi=100) + assert tuple(fig.get_size_inches()) == figsize_inches + + +def test_figsize_invalid_unit(): + with pytest.raises(ValueError, match="Invalid unit 'um'"): + plt.figure(figsize=(6, 4, "um")) diff --git a/lib/matplotlib/tests/test_font_manager.py b/lib/matplotlib/tests/test_font_manager.py index d15b892b3eea..1b6e7b4778a1 100644 --- a/lib/matplotlib/tests/test_font_manager.py +++ b/lib/matplotlib/tests/test_font_manager.py @@ -15,12 +15,13 @@ from matplotlib.font_manager import ( findfont, findSystemFonts, FontEntry, FontProperties, fontManager, json_dump, json_load, get_font, is_opentype_cff_font, - MSUserFontDirectories, _get_fontconfig_fonts, ttfFontProperty) + MSUserFontDirectories, ttfFontProperty, + _get_fontconfig_fonts, _normalize_weight) from matplotlib import cbook, ft2font, pyplot as plt, rc_context, figure as mfigure from matplotlib.testing import subprocess_run_helper, subprocess_run_for_testing -has_fclist = shutil.which('fc-list') is not None +has_fclist = sys.platform != 'emscripten' and shutil.which('fc-list') is not None def test_font_priority(): @@ -164,7 +165,7 @@ def test_user_fonts_linux(tmpdir, monkeypatch): # Prepare a temporary user font directory user_fonts_dir = tmpdir.join('fonts') user_fonts_dir.ensure(dir=True) - shutil.copyfile(Path(__file__).parent / font_test_file, + shutil.copyfile(Path(__file__).parent / 'data' / font_test_file, user_fonts_dir.join(font_test_file)) with monkeypatch.context() as m: @@ -181,7 +182,7 @@ def test_user_fonts_linux(tmpdir, monkeypatch): def test_addfont_as_path(): """Smoke test that addfont() accepts pathlib.Path.""" font_test_file = 'mpltest.ttf' - path = Path(__file__).parent / font_test_file + path = Path(__file__).parent / 'data' / font_test_file try: fontManager.addfont(path) added, = (font for font in fontManager.ttflist @@ -215,7 +216,7 @@ def test_user_fonts_win32(): os.makedirs(user_fonts_dir) # Copy the test font to the user font directory - shutil.copy(Path(__file__).parent / font_test_file, user_fonts_dir) + shutil.copy(Path(__file__).parent / 'data' / font_test_file, user_fonts_dir) # Now, the font should be available fonts = findSystemFonts() @@ -228,6 +229,8 @@ def _model_handler(_): plt.close() +@pytest.mark.skipif(sys.platform == 'emscripten', + reason='emscripten does not support subprocesses') @pytest.mark.skipif(not hasattr(os, "register_at_fork"), reason="Cannot register at_fork handlers") def test_fork(): @@ -407,3 +410,43 @@ def test_fontproperties_init_deprecation(): # Since this case is not covered by docs, I've refrained from jumping # extra hoops to detect this possible API misuse. FontProperties(family="serif-24:style=oblique:weight=bold") + + +def test_normalize_weights(): + assert _normalize_weight(300) == 300 # passthrough + assert _normalize_weight('ultralight') == 100 + assert _normalize_weight('light') == 200 + assert _normalize_weight('normal') == 400 + assert _normalize_weight('regular') == 400 + assert _normalize_weight('book') == 400 + assert _normalize_weight('medium') == 500 + assert _normalize_weight('roman') == 500 + assert _normalize_weight('semibold') == 600 + assert _normalize_weight('demibold') == 600 + assert _normalize_weight('demi') == 600 + assert _normalize_weight('bold') == 700 + assert _normalize_weight('heavy') == 800 + assert _normalize_weight('extra bold') == 800 + assert _normalize_weight('black') == 900 + with pytest.raises(KeyError): + _normalize_weight('invalid') + + +def test_font_match_warning(caplog): + findfont(FontProperties(family=["DejaVu Sans"], weight=750)) + logs = [rec.message for rec in caplog.records] + assert 'findfont: Failed to find font weight 750, now using 700.' in logs + + +def test_mutable_fontproperty_cache_invalidation(): + fp = FontProperties() + assert findfont(fp).endswith("DejaVuSans.ttf") + fp.set_weight("bold") + assert findfont(fp).endswith("DejaVuSans-Bold.ttf") + + +def test_fontproperty_default_cache_invalidation(): + mpl.rcParams["font.weight"] = "normal" + assert findfont("DejaVu Sans").endswith("DejaVuSans.ttf") + mpl.rcParams["font.weight"] = "bold" + assert findfont("DejaVu Sans").endswith("DejaVuSans-Bold.ttf") diff --git a/lib/matplotlib/tests/test_ft2font.py b/lib/matplotlib/tests/test_ft2font.py index 7dc851b2c9cf..8b448e17b7fd 100644 --- a/lib/matplotlib/tests/test_ft2font.py +++ b/lib/matplotlib/tests/test_ft2font.py @@ -7,7 +7,8 @@ import matplotlib as mpl from matplotlib import ft2font -from matplotlib.testing.decorators import check_figures_equal +from matplotlib.testing import _gen_multi_font_text +from matplotlib.testing.decorators import image_comparison import matplotlib.font_manager as fm import matplotlib.path as mpath import matplotlib.pyplot as plt @@ -17,7 +18,8 @@ def test_ft2image_draw_rect_filled(): width = 23 height = 42 for x0, y0, x1, y1 in itertools.product([1, 100], [2, 200], [4, 400], [8, 800]): - im = ft2font.FT2Image(width, height) + with pytest.warns(mpl.MatplotlibDeprecationWarning): + im = ft2font.FT2Image(width, height) im.draw_rect_filled(x0, y0, x1, y1) a = np.asarray(im) assert a.dtype == np.uint8 @@ -40,6 +42,7 @@ def test_ft2font_dejavu_attrs(): assert font.family_name == 'DejaVu Sans' assert font.style_name == 'Book' assert font.num_faces == 1 # Single TTF. + assert font.num_named_instances == 0 # Not a variable font. assert font.num_glyphs == 6241 # From compact encoding view in FontForge. assert font.num_fixed_sizes == 0 # All glyphs are scalable. assert font.num_charmaps == 5 @@ -73,6 +76,7 @@ def test_ft2font_cm_attrs(): assert font.family_name == 'cmtt10' assert font.style_name == 'Regular' assert font.num_faces == 1 # Single TTF. + assert font.num_named_instances == 0 # Not a variable font. assert font.num_glyphs == 133 # From compact encoding view in FontForge. assert font.num_fixed_sizes == 0 # All glyphs are scalable. assert font.num_charmaps == 2 @@ -105,6 +109,7 @@ def test_ft2font_stix_bold_attrs(): assert font.family_name == 'STIXSizeTwoSym' assert font.style_name == 'Bold' assert font.num_faces == 1 # Single TTF. + assert font.num_named_instances == 0 # Not a variable font. assert font.num_glyphs == 20 # From compact encoding view in FontForge. assert font.num_fixed_sizes == 0 # All glyphs are scalable. assert font.num_charmaps == 3 @@ -819,7 +824,7 @@ def test_ft2font_drawing(): np.testing.assert_array_equal(image, expected) font = ft2font.FT2Font(file, hinting_factor=1, _kerning_factor=0) glyph = font.load_char(ord('M')) - image = ft2font.FT2Image(expected.shape[1], expected.shape[0]) + 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) @@ -852,49 +857,15 @@ def test_ft2font_get_path(): np.testing.assert_array_equal(codes, expected_codes) -@pytest.mark.parametrize('family_name, file_name', - [("WenQuanYi Zen Hei", "wqy-zenhei.ttc"), - ("Noto Sans CJK JP", "NotoSansCJK.ttc"), - ("Noto Sans TC", "NotoSansTC-Regular.otf")] - ) -def test_fallback_smoke(family_name, file_name): - fp = fm.FontProperties(family=[family_name]) - if Path(fm.findfont(fp)).name != file_name: - pytest.skip(f"Font {family_name} ({file_name}) is missing") - plt.rcParams['font.size'] = 20 +@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.05, 0.45, "There are 几个汉字 in between!", - family=['DejaVu Sans', family_name]) - fig.text(0.05, 0.85, "There are 几个汉字 in between!", - family=[family_name]) + fig.text(0.5, 0.5, test_str, + horizontalalignment='center', verticalalignment='center') - # TODO enable fallback for other backends! - for fmt in ['png', 'raw']: # ["svg", "pdf", "ps"]: - fig.savefig(io.BytesIO(), format=fmt) - - -@pytest.mark.parametrize('family_name, file_name', - [("WenQuanYi Zen Hei", "wqy-zenhei"), - ("Noto Sans CJK JP", "NotoSansCJK"), - ("Noto Sans TC", "NotoSansTC-Regular.otf")] - ) -@check_figures_equal(extensions=["png", "pdf", "eps", "svg"]) -def test_font_fallback_chinese(fig_test, fig_ref, family_name, file_name): - fp = fm.FontProperties(family=[family_name]) - if file_name not in Path(fm.findfont(fp)).name: - pytest.skip(f"Font {family_name} ({file_name}) is missing") - - text = ["There are", "几个汉字", "in between!"] - - plt.rcParams["font.size"] = 20 - test_fonts = [["DejaVu Sans", family_name]] * 3 - ref_fonts = [["DejaVu Sans"], [family_name], ["DejaVu Sans"]] - - for j, (txt, test_font, ref_font) in enumerate( - zip(text, test_fonts, ref_fonts) - ): - fig_ref.text(0.05, .85 - 0.15*j, txt, family=ref_font) - fig_test.text(0.05, .85 - 0.15*j, txt, family=test_font) + fig.savefig(io.BytesIO(), format=fmt) @pytest.mark.parametrize("font_list", @@ -912,29 +883,31 @@ def test_fallback_missing(recwarn, font_list): assert all([font in recwarn[0].message.args[0] for font in font_list]) -@pytest.mark.parametrize( - "family_name, file_name", - [ - ("WenQuanYi Zen Hei", "wqy-zenhei"), - ("Noto Sans CJK JP", "NotoSansCJK"), - ("Noto Sans TC", "NotoSansTC-Regular.otf") - ], -) -def test__get_fontmap(family_name, file_name): - fp = fm.FontProperties(family=[family_name]) - found_file_name = Path(fm.findfont(fp)).name - if file_name not in found_file_name: - pytest.skip(f"Font {family_name} ({file_name}) is missing") - - text = "There are 几个汉字 in between!" +@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=["DejaVu Sans", family_name]) - ) + fm.fontManager._find_fonts_by_props(fm.FontProperties(family=fonts)) ) - fontmap = ft._get_fontmap(text) + fontmap = ft._get_fontmap(test_str) for char, font in fontmap.items(): - if ord(char) > 127: - assert Path(font.fname).name == found_file_name + 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 == "DejaVuSans.ttf" + assert Path(font.fname).name == 'cmr10.ttf' diff --git a/lib/matplotlib/tests/test_getattr.py b/lib/matplotlib/tests/test_getattr.py index f0f5823600ca..fe302220067a 100644 --- a/lib/matplotlib/tests/test_getattr.py +++ b/lib/matplotlib/tests/test_getattr.py @@ -1,25 +1,29 @@ from importlib import import_module from pkgutil import walk_packages +import sys +import warnings -import matplotlib import pytest +import matplotlib +from matplotlib.testing import is_ci_environment, subprocess_run_helper + # 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('.')) -] +module_names = [] +backend_module_names = [] +for m in walk_packages(path=matplotlib.__path__, prefix=f'{matplotlib.__name__}.'): + if m.name.startswith(__package__): + continue + if any(x.startswith('_') for x in m.name.split('.')): + continue + if 'backends.backend_' in m.name: + backend_module_names.append(m.name) + else: + module_names.append(m.name) -@pytest.mark.parametrize('module_name', module_names) -@pytest.mark.filterwarnings('ignore::DeprecationWarning') -@pytest.mark.filterwarnings('ignore::ImportWarning') -def test_getattr(module_name): +def _test_getattr(module_name, use_pytest=True): """ Test that __getattr__ methods raise AttributeError for unknown keys. See #20822, #20855. @@ -28,8 +32,35 @@ def test_getattr(module_name): 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}') + if use_pytest: + pytest.skip(f'Cannot import {module_name} due to {e}') + else: + print(f'SKIP: Cannot import {module_name} due to {e}') + return key = 'THIS_SYMBOL_SHOULD_NOT_EXIST' if hasattr(module, key): delattr(module, key) + + +@pytest.mark.parametrize('module_name', module_names) +@pytest.mark.filterwarnings('ignore::DeprecationWarning') +@pytest.mark.filterwarnings('ignore::ImportWarning') +def test_getattr(module_name): + _test_getattr(module_name) + + +def _test_module_getattr(): + warnings.filterwarnings('ignore', category=DeprecationWarning) + warnings.filterwarnings('ignore', category=ImportWarning) + module_name = sys.argv[1] + _test_getattr(module_name, use_pytest=False) + + +@pytest.mark.parametrize('module_name', backend_module_names) +def test_backend_getattr(module_name): + proc = subprocess_run_helper(_test_module_getattr, module_name, + timeout=120 if is_ci_environment() else 20) + if 'SKIP: ' in proc.stdout: + pytest.skip(proc.stdout.removeprefix('SKIP: ')) + print(proc.stdout) diff --git a/lib/matplotlib/tests/test_gridspec.py b/lib/matplotlib/tests/test_gridspec.py index deda73c3b6ab..625a79816ed3 100644 --- a/lib/matplotlib/tests/test_gridspec.py +++ b/lib/matplotlib/tests/test_gridspec.py @@ -1,3 +1,4 @@ +import matplotlib import matplotlib.gridspec as gridspec import matplotlib.pyplot as plt import pytest @@ -9,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. @@ -27,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]" diff --git a/lib/matplotlib/tests/test_image.py b/lib/matplotlib/tests/test_image.py index 24a0ab929bbf..649e345b3613 100644 --- a/lib/matplotlib/tests/test_image.py +++ b/lib/matplotlib/tests/test_image.py @@ -1,5 +1,4 @@ from contextlib import ExitStack -from copy import copy import functools import io import os @@ -9,7 +8,7 @@ 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 @@ -17,13 +16,26 @@ colors, image as mimage, patches, pyplot as plt, style, rcParams) from matplotlib.image import (AxesImage, BboxImage, FigureImage, NonUniformImage, PcolorImage) +from matplotlib.patches import Rectangle 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 +@pytest.fixture +def nonaffine_identity(): + """Non-affine identity transform for compositing with any affine transform""" + class NonAffineIdentityTransform(Transform): + input_dims = 2 + output_dims = 2 + + def inverted(self): + return self + return NonAffineIdentityTransform() + + @image_comparison(['interp_alpha.png'], remove_text=True) def test_alpha_interp(): """Test the interpolation of the alpha channel on RGBA images""" @@ -37,7 +49,7 @@ def test_alpha_interp(): axr.imshow(img, interpolation="bilinear") -@image_comparison(['interp_nearest_vs_none'], +@image_comparison(['interp_nearest_vs_none'], tol=3.7, # For Ghostscript 10.06+. extensions=['pdf', 'svg'], remove_text=True) def test_interp_nearest_vs_none(): """Test the effect of "nearest" and "none" interpolation""" @@ -88,7 +100,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) @@ -104,7 +116,7 @@ def test_imshow_antialiased(fig_test, fig_ref, 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) @@ -114,12 +126,12 @@ def test_imshow_zoom(fig_test, fig_ref): fig.set_size_inches(2.9, 2.9) ax = fig_test.subplots() ax.imshow(A, interpolation='auto') - ax.set_xlim([10, 20]) - ax.set_ylim([10, 20]) + 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]) + ax.set_xlim(10, 20) + ax.set_ylim(10, 20) @check_figures_equal() @@ -184,6 +196,28 @@ 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 @@ -193,8 +227,8 @@ def test_imsave_rgba_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) +def test_imsave_fspath(fmt, tmp_path): + plt.imsave(tmp_path / f'unused.{fmt}', np.array([[0, 1]]), format=fmt) def test_imsave_color_alpha(): @@ -256,19 +290,19 @@ def test_image_alpha(): @mpl.style.context('mpl20') -@check_figures_equal(extensions=['png']) +@check_figures_equal() def test_imshow_alpha(fig_test, fig_ref): np.random.seed(19680801) - rgbf = np.random.rand(6, 6, 3) + 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=0.5) - ax3.imshow(rgbu, 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) + 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 @@ -281,6 +315,33 @@ def test_imshow_alpha(fig_test, fig_ref): 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 + + 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() + + # Draw should not modify original array + np.testing.assert_array_equal(array, im._A) + + def test_cursor_data(): from matplotlib.backend_bases import MouseEvent @@ -404,6 +465,43 @@ def test_format_cursor_data(data, text): assert im.format_cursor_data(im.get_cursor_data(event)) == text +@pytest.mark.parametrize( + "data, text", [ + ([[[10001, 10000]], [[0, 0]]], "[10001.000, 0.000]"), + ([[[.123, .987]], [[0.1, 0]]], "[0.123, 0.100]"), + ([[[np.nan, 1, 2]], [[0, 0, 0]]], "[]"), + ]) +def test_format_cursor_data_multinorm(data, text): + from matplotlib.backend_bases import MouseEvent + fig, ax = plt.subplots() + cmap_bivar = mpl.bivar_colormaps['BiOrangeBlue'] + cmap_multivar = mpl.multivar_colormaps['2VarAddA'] + + # This is a test for ColorizingArtist._format_cursor_data_override() + # with data with multiple channels. + # It includes a workaround so that we can test this functionality + # before the MultiVar/BiVariate colormaps and MultiNorm are exposed + # via the top-level methods (ax.imshow()) + # i.e. we here set the hidden variables _cmap and _norm + # and use set_array() on the ColorizingArtist rather than the _ImageBase + # but this workaround should be replaced by: + # `ax.imshow(data, cmap=cmap_bivar, vmin=(0,0), vmax=(1,1))` + # once the functionality is available. + # see https://github.com/matplotlib/matplotlib/issues/14168 + im = ax.imshow([[0, 1]]) + im.colorizer._cmap = cmap_bivar + im.colorizer._norm = colors.MultiNorm([im.norm, im.norm]) + mpl.colorizer.ColorizingArtist.set_array(im, data) + + xdisp, ydisp = ax.transData.transform([0, 0]) + event = MouseEvent('motion_notify_event', fig.canvas, xdisp, ydisp) + assert im.format_cursor_data(im.get_cursor_data(event)) == text + + im.colorizer._cmap = cmap_multivar + event = MouseEvent('motion_notify_event', fig.canvas, xdisp, ydisp) + assert im.format_cursor_data(im.get_cursor_data(event)) == text + + @image_comparison(['image_clip'], style='mpl20') def test_image_clip(): d = [[1, 2], [3, 4]] @@ -426,7 +524,7 @@ def test_image_cliprect(): im.set_clip_path(rect) -@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)) @@ -477,7 +575,7 @@ def test_image_shift(): def test_image_edges(): fig = plt.figure(figsize=[1, 1]) - ax = fig.add_axes([0, 0, 1, 1], frameon=False) + ax = fig.add_axes((0, 0, 1, 1), frameon=False) data = np.tile(np.arange(12), 15).reshape(20, 9) @@ -485,8 +583,8 @@ def test_image_edges(): interpolation='none', cmap='gray') x = y = 2 - ax.set_xlim([-x, x]) - ax.set_ylim([-y, y]) + ax.set_xlim(-x, x) + ax.set_ylim(-y, y) ax.set_xticks([]) ax.set_yticks([]) @@ -511,10 +609,10 @@ def test_image_composite_background(): ax.imshow(arr, extent=[0, 2, 15, 0]) ax.imshow(arr, extent=[4, 6, 15, 0]) ax.set_facecolor((1, 0, 0, 0.5)) - ax.set_xlim([0, 12]) + 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 @@ -537,8 +635,8 @@ def test_image_composite_alpha(): ax.imshow(arr2, extent=[0, 5, 2, 3], alpha=0.6) ax.imshow(arr2, extent=[0, 5, 3, 4], alpha=0.3) ax.set_facecolor((0, 0.5, 0, 1)) - ax.set_xlim([0, 5]) - ax.set_ylim([5, 0]) + ax.set_xlim(0, 5) + ax.set_ylim(5, 0) @check_figures_equal(extensions=["pdf"]) @@ -863,7 +961,7 @@ def test_mask_image_over_under(): (2 * np.pi * 0.5 * 1.5)) Z = 10*(Z2 - Z1) # difference of Gaussians - palette = plt.cm.gray.with_extremes(over='r', under='g', bad='b') + 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', @@ -1119,22 +1217,7 @@ def test_respects_bbox(): assert buf_before.getvalue() != buf_after.getvalue() # Not all white. -def test_image_cursor_formatting(): - fig, ax = plt.subplots() - # Create a dummy image to be able to call format_cursor_data - im = ax.imshow(np.zeros((4, 4))) - - data = np.ma.masked_array([0], mask=[True]) - assert im.format_cursor_data(data) == '[]' - - data = np.ma.masked_array([0], mask=[False]) - assert im.format_cursor_data(data) == '[0]' - - data = np.nan - 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) @@ -1160,12 +1243,11 @@ def test_image_array_alpha_validation(): @mpl.style.context('mpl20') def test_exact_vmin(): - cmap = copy(mpl.colormaps["autumn_r"]) - cmap.set_under(color="lightgrey") + cmap = mpl.colormaps["autumn_r"].with_extremes(under="lightgrey") # make the image exactly 190 pixels wide fig = plt.figure(figsize=(1.9, 0.1), dpi=100) - ax = fig.add_axes([0, 0, 1, 1]) + ax = fig.add_axes((0, 0, 1, 1)) data = np.array( [[-1, -1, -1, 0, 0, 0, 0, 43, 79, 95, 66, 1, -1, -1, -1, 0, 0, 0, 34]], @@ -1287,7 +1369,7 @@ def test_imshow_quantitynd(): fig.canvas.draw() -@check_figures_equal(extensions=['png']) +@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) @@ -1316,7 +1398,7 @@ def test_norm_change(fig_test, fig_ref): @pytest.mark.parametrize('x', [-1, 1]) -@check_figures_equal(extensions=['png']) +@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) @@ -1435,15 +1517,13 @@ def test_rgba_antialias(): aa[70:90, 195:215] = 1e6 aa[20:30, 195:215] = -1e6 - cmap = copy(plt.cm.RdBu_r) - cmap.set_over('yellow') - cmap.set_under('cyan') + cmap = plt.colormaps["RdBu_r"].with_extremes(over='yellow', 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]) + 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) @@ -1459,7 +1539,7 @@ def test_rgba_antialias(): cmap=cmap, vmin=-1.2, vmax=1.2) -@check_figures_equal(extensions=('png', )) +@check_figures_equal() def test_upsample_interpolation_stage(fig_test, fig_ref): """ Show that interpolation_stage='auto' gives the same as 'data' @@ -1479,7 +1559,7 @@ def test_upsample_interpolation_stage(fig_test, fig_ref): interpolation_stage='auto') -@check_figures_equal(extensions=('png', )) +@check_figures_equal() def test_downsample_interpolation_stage(fig_test, fig_ref): """ Show that interpolation_stage='auto' gives the same as 'rgba' @@ -1488,7 +1568,7 @@ def test_downsample_interpolation_stage(fig_test, fig_ref): # Fixing random state for reproducibility np.random.seed(19680801) - grid = np.random.rand(4000, 4000) + grid = np.random.rand(400, 400) ax = fig_ref.subplots() ax.imshow(grid, interpolation='auto', cmap='viridis', interpolation_stage='rgba') @@ -1515,8 +1595,8 @@ def test_rc_interpolation_stage(): @pytest.mark.parametrize( 'dim, size, msg', [['row', 2**23, r'2\*\*23 columns'], ['col', 2**24, r'2\*\*24 rows']]) -@check_figures_equal(extensions=('png', )) -def test_large_image(fig_test, fig_ref, dim, size, msg, origin): +@check_figures_equal() +def test_large_image(fig_test, fig_ref, dim, size, msg, origin, high_memory): # 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. @@ -1535,10 +1615,12 @@ def test_large_image(fig_test, fig_ref, dim, size, msg, origin): with pytest.warns(UserWarning, match=f'Data with more than {msg} cannot be ' 'accurately displayed.'): - fig_test.canvas.draw() + with io.BytesIO() as buffer: + # Write to a buffer to trigger the warning + fig_test.savefig(buffer) - array = np.zeros((1, 2)) - array[:, 1] = 1 + array = np.zeros((1, size // 2 + 1)) + array[:, array.size // 2:] = 1 if dim == 'col': array = array.T im = ax_ref.imshow(array, vmin=0, vmax=1, aspect='auto', @@ -1547,7 +1629,7 @@ def test_large_image(fig_test, fig_ref, dim, size, msg, origin): origin=origin) -@check_figures_equal(extensions=["png"]) +@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) @@ -1592,6 +1674,50 @@ def test__resample_valid_output(): 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.2, 0.3, 0.4, 0.5, 0.6]]), mimage.NEAREST, + np.array([[0.1, 0.2, 0.2, 0.3, 0.4, 0.4, 0.5, 0.6, 0.6]])), + (np.array([[0.1, 0.3, 0.2]]), mimage.BILINEAR, + np.array([[0.1, 0.1, 0.15, 0.21, 0.27, 0.285, 0.255, 0.225, 0.2, 0.2]])), + (np.array([[0.1, 0.9]]), mimage.BILINEAR, + np.array([[0.1, 0.1, 0.1, 0.1, 0.1, 0.14, 0.22, 0.3, 0.38, 0.46, + 0.54, 0.62, 0.7, 0.78, 0.86, 0.9, 0.9, 0.9, 0.9, 0.9]])), + (np.array([[0.1, 0.1]]), mimage.BILINEAR, np.full((1, 10), 0.1)), + # Test at the subpixel level + (np.array([[0.1, 0.9]]), mimage.NEAREST, + np.concatenate([np.full(512, 0.1), np.full(512, 0.9)]).reshape(1, -1)), + (np.array([[0.1, 0.9]]), mimage.BILINEAR, + np.concatenate([np.full(256, 0.1), + np.linspace(0.5, 256, 512).astype(int) / 256 * 0.8 + 0.1, + np.full(256, 0.9)]).reshape(1, -1)), + ] +) +def test_resample_nonaffine(data, interpolation, expected, nonaffine_identity): + # Test that both affine and nonaffine transforms resample to the correct answer + + # If the array is constant, the tolerance can be tight + # Otherwise, the tolerance is limited by the subpixel approach in the agg backend + atol = 0 if np.all(data == data.ravel()[0]) else 2e-3 + + # 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, atol=atol) + + # Create a nonaffine version of the same transform + # by compositing with a nonaffine identity transform + nonaffine_transform = nonaffine_identity + affine_transform + + nonaffine_result = np.empty_like(expected) + mimage.resample(data, nonaffine_result, nonaffine_transform, + interpolation=interpolation) + assert_allclose(nonaffine_result, expected, atol=atol) + + def test_axesimage_get_shape(): # generate dummy image to test get_shape method ax = plt.gca() @@ -1658,8 +1784,7 @@ def test_downsampling_speckle(): axs = axs.flatten() img = ((np.arange(1024).reshape(-1, 1) * np.ones(720)) // 50).T - cm = plt.get_cmap("viridis") - cm.set_over("m") + cm = plt.get_cmap("viridis").with_extremes(over="m") norm = colors.LogNorm(vmin=3, vmax=11) # old default cannot be tested because it creates over/under speckles @@ -1711,3 +1836,220 @@ def test_resample_dtypes(dtype, ndim): 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 + # alpha param (both scalar and array alpha multiply the existing alpha) + 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 multiplied array alpha + (im_rgb, array_alpha.reshape((ny, nx, 1)) + * new_array_alpha.reshape((ny, nx, 1))), axis=-1 + ), interpolation_stage=intp_stage + ) + + +@image_comparison(['nn_pixel_alignment.png']) +def test_nn_pixel_alignment(nonaffine_identity): + fig, axs = plt.subplots(2, 3) + + for j, N in enumerate([3, 7, 11]): + # In each column, the plots use the same data array + data = np.arange(N**2).reshape((N, N)) % 4 + seps = np.arange(-0.5, N) + + for i in range(2): + if i == 0: + # Top row uses an affine transform + axs[i, j].imshow(data, cmap='Grays', interpolation='nearest') + else: + # Bottom row uses a non-affine transform + axs[i, j].imshow(data, cmap='Grays', interpolation='nearest', + transform=nonaffine_identity + axs[i, j].transData) + + axs[i, j].set_axis_off() + axs[i, j].vlines(seps, -1, N, lw=0.5, color='red', ls='dashed') + axs[i, j].hlines(seps, -1, N, lw=0.5, color='red', ls='dashed') + + +@image_comparison(['alignment_half_display_pixels.png'], style='mpl20') +def test_alignment_half_display_pixels(nonaffine_identity): + # All values in this test are chosen carefully so that many display pixels are + # aligned with an edge or a corner of an input pixel + + # Layout: + # Top row is origin='upper', bottom row is origin='lower' + # Column 1: affine transform, anchored at whole pixel + # Column 2: affine transform, anchored at half pixel + # Column 3: nonaffine transform, anchored at whole pixel + # Column 4: nonaffine transform, anchored at half pixel + # Column 5: affine transform, anchored at half pixel, interpolation='hanning' + + # Each axes patch is magenta, so seeing a magenta line at an edge of the image + # means that the image is not filling the axes + + fig = plt.figure(figsize=(5, 2), dpi=100) + fig.set_facecolor('g') + + corner_x = [0.01, 0.199, 0.41, 0.599, 0.809] + corner_y = [0.05, 0.53] + + axs = [] + for cy in corner_y: + for ix, cx in enumerate(corner_x): + my = cy + 0.0125 if ix in [1, 3, 4] else cy + axs.append(fig.add_axes([cx, my, 0.17, 0.425], xticks=[], yticks=[])) + + # Verify that each axes has been created with the correct width/height and that all + # four corners are on whole pixels (columns 1 and 3) or half pixels (columns 2, 4, + # and 5) + for i, ax in enumerate(axs): + extents = ax.get_window_extent().extents + assert_allclose(extents[2:4] - extents[0:2], 85, rtol=0, atol=1e-13) + assert_allclose(extents % 1, 0.5 if i % 5 in [1, 3, 4] else 0, + rtol=0, atol=1e-13) + + N = 10 + + data = np.arange(N**2).reshape((N, N)) % 9 + seps = np.arange(-0.5, N) + + for i, ax in enumerate(axs): + ax.set_facecolor('m') + + transform = nonaffine_identity + ax.transData if i % 4 >= 2 else ax.transData + ax.imshow(data, cmap='Blues', + interpolation='hanning' if i % 5 == 4 else 'nearest', + origin='upper' if i >= 5 else 'lower', + transform=transform) + + ax.vlines(seps, -0.5, N - 0.5, lw=0.5, color='red', ls=(0, (2, 4))) + ax.hlines(seps, -0.5, N - 0.5, lw=0.5, color='red', ls=(0, (2, 4))) + + for spine in ax.spines: + ax.spines[spine].set_linestyle((0, (5, 10))) + + +@image_comparison(['image_bounds_handling.png'], tol=0.006) +def test_image_bounds_handling(nonaffine_identity): + # TODO: The second and third panels in the bottom row show that the handling of + # image bounds is bugged for non-affine transforms and non-nearest-neighbor + # interpolation. If this bug gets fixed, the baseline image should be updated. + + fig, axs = plt.subplots(2, 3) + + N = 11 + + for j, interpolation in enumerate(['nearest', 'hanning', 'bilinear']): + data = np.arange(N**2).reshape((N, N)) + data = data / N**2 + (data % 4) / 6 + rotation = Affine2D().rotate_around(N/2-0.5, N/2-0.5, 1) + + for i in range(2): + transform = rotation + axs[i, j].transData + if i == 1: + # Bottom row uses a non-affine transform + transform = nonaffine_identity + transform + + axs[i, j].imshow(data, cmap='Grays', interpolation=interpolation, + transform=transform) + + axs[i, j].set_axis_off() + box = Rectangle((-0.5, -0.5), N, N, + edgecolor='red', facecolor='none', lw=0.5, ls='dashed', + transform=rotation + axs[i, j].transData) + axs[i, j].add_artist(box) + + +@image_comparison(['rgba_clean_edges.png'], tol=0.003) +def test_rgba_clean_edges(): + np.random.seed(19680801+9) # same as in test_upsampling() + data = np.random.rand(8, 8) + data = np.stack([data, data]) + data[1, 2:4, 2:4] = np.nan + + rotation = Affine2D().rotate_around(3.5, 3.5, 1) + + fig, axs = plt.subplots(1, 2) + + for i in range(2): + # Add background patches to check the fading to non-white colors + black = Rectangle((3.75, 2), 5, 5, color='black', zorder=0) + gray = Rectangle((0, -2), 3.75, 4, color='gray', zorder=0) + partly_black = Rectangle((3.75, -2), 5, 4, fc='black', ec='none', + alpha=0.5, zorder=0) + axs[i].add_patch(black) + axs[i].add_patch(gray) + axs[i].add_patch(partly_black) + + axs[i].imshow(data[i, ...], + interpolation='bilinear', interpolation_stage='rgba', + transform=rotation + axs[i].transData) + + axs[i].set_axis_off() + axs[i].set_xlim(-2.5, 9.5) + axs[i].set_ylim(-2.5, 9.5) + + +@image_comparison(['affine_fill_to_edges.png']) +def test_affine_fill_to_edges(): + # The two rows show the two settings of origin + # The three columns show the original and the two mirror flips + fig, axs = plt.subplots(2, 3) + + N = 7 + data = np.arange(N**2).reshape((N, N)) % 3 + + transform = [Affine2D(), + Affine2D().translate(0, -N + 1).scale(1, -1), + Affine2D().translate(-N + 1, 0).scale(-1, 1)] + + for j in range(3): + for i in range(2): + origin = 'upper' if i == 0 else 'lower' + + axs[i, j].imshow(data, cmap='Grays', + interpolation='hanning', origin=origin, + transform=transform[j] + axs[i, j].transData) + + axs[i, j].set_axis_off() + axs[i, j].vlines([-0.5, N - 0.5], -1, 2, lw=0.5, color='red') + axs[i, j].vlines([-0.5, N - 0.5], N - 3, N, lw=0.5, color='red') + axs[i, j].hlines([-0.5, N - 0.5], -1, 2, lw=0.5, color='red') + axs[i, j].hlines([-0.5, N - 0.5], N - 3, N, lw=0.5, color='red') diff --git a/lib/matplotlib/tests/test_inset.py b/lib/matplotlib/tests/test_inset.py index c25580214c80..e368a4af4e1b 100644 --- a/lib/matplotlib/tests/test_inset.py +++ b/lib/matplotlib/tests/test_inset.py @@ -4,6 +4,7 @@ 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 @@ -13,7 +14,7 @@ def test_indicate_inset_no_args(): ax.indicate_inset() -@check_figures_equal(extensions=["png"]) +@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() @@ -104,3 +105,38 @@ def test_zoom_inset_connector_styles(): # 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 61892378bd03..bc3bd4e97f8c 100644 --- a/lib/matplotlib/tests/test_legend.py +++ b/lib/matplotlib/tests/test_legend.py @@ -1,4 +1,5 @@ import collections +import io import itertools import platform import time @@ -41,7 +42,19 @@ 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, ax = plt.subplots() @@ -51,7 +64,7 @@ def test_legend_auto1(): 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, ax = plt.subplots() @@ -61,7 +74,7 @@ def test_legend_auto2(): 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, ax = plt.subplots() @@ -127,7 +140,7 @@ def test_legend_auto5(): assert_allclose(leg_bboxes[1].bounds, leg_bboxes[0].bounds) -@image_comparison(['legend_various_labels'], remove_text=True) +@image_comparison(['legend_various_labels.png'], remove_text=True) def test_various_labels(): # tests all sorts of label types fig = plt.figure() @@ -139,7 +152,7 @@ def test_various_labels(): @image_comparison(['legend_labels_first.png'], remove_text=True, - tol=0.013 if platform.machine() == 'arm64' else 0) + tol=0 if platform.machine() == 'x86_64' else 0.013) def test_labels_first(): # test labels to left of markers fig, ax = plt.subplots() @@ -150,7 +163,7 @@ def test_labels_first(): @image_comparison(['legend_multiple_keys.png'], remove_text=True, - tol=0.013 if platform.machine() == 'arm64' else 0) + tol=0 if platform.machine() == 'x86_64' else 0.013) def test_multiple_keys(): # test legend entries with multiple keys fig, ax = plt.subplots() @@ -186,7 +199,7 @@ def test_alpha_rcparam(): leg.legendPatch.set_facecolor([1, 0, 0, 0.5]) -@image_comparison(['fancy'], remove_text=True, tol=0.05) +@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 @@ -209,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() @@ -226,7 +239,7 @@ 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"] @@ -279,7 +292,7 @@ def test_hatching(): def test_legend_remove(): fig, ax = plt.subplots() lines = ax.plot(range(10)) - leg = fig.legend(lines, "test") + leg = fig.legend(lines, ["test"]) leg.remove() assert fig.legends == [] leg = ax.legend("test") @@ -305,7 +318,7 @@ def test_reverse_legend_handles_and_labels(): assert actual_markers == list(reversed(markers)) -@check_figures_equal(extensions=["png"]) +@check_figures_equal() def test_reverse_legend_display(fig_test, fig_ref): """Check that the rendered legend entries are reversed""" ax = fig_test.subplots() @@ -389,17 +402,14 @@ def test_legend_kwargs_handles_labels(self): 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(DeprecationWarning) 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).startswith( - "You have mixed positional and keyword arguments, some input may " - "be discarded.") def test_parasite(self): from mpl_toolkits.axes_grid1 import host_subplot # type: ignore[import] @@ -414,6 +424,15 @@ def test_parasite(self): plt.legend() Legend.assert_called_with(host, [p1, p2], ['Density', 'Temperature']) + def test_legend_warns_on_unequal_number_of_handles_and_labels(self): + fig, ax = plt.subplots() + line1, = ax.plot([1, 2]) + line2, = ax.plot([3, 4]) + with pytest.warns(UserWarning, match="Mismatched number of handles and labels"): + ax.legend([line1, line2], ['only_one']) # 2 handles, 1 label + with pytest.warns(UserWarning, match="Mismatched number of handles and labels"): + ax.legend([line1], ['label_a', 'label_b']) # 1 handle, 2 labels + class TestLegendFigureFunction: # Tests the legend function for figure @@ -459,16 +478,13 @@ 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(DeprecationWarning) 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).startswith( - "You have mixed positional and keyword arguments, some input may " - "be discarded.") def test_figure_legend_outside(): @@ -506,14 +522,14 @@ def test_figure_legend_outside(): 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]) + assert_allclose(axs.get_window_extent().extents, axbb[nn], + rtol=1e-4) + assert_allclose(leg.get_window_extent().extents, legbb[nn], + rtol=1e-4) @image_comparison(['legend_stackplot.png'], - tol=0.031 if platform.machine() == 'arm64' else 0) + 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 @@ -523,8 +539,8 @@ def test_legend_stackplot(): y2 = 2.0 * x + 1 y3 = 3.0 * x + 2 ax.stackplot(x, y1, y2, y3, labels=['y1', 'y2', 'y3']) - ax.set_xlim((0, 10)) - ax.set_ylim((0, 70)) + ax.set_xlim(0, 10) + ax.set_ylim(0, 70) ax.legend(loc='best') @@ -649,7 +665,7 @@ def test_empty_bar_chart_with_legend(): @image_comparison(['shadow_argument_types.png'], remove_text=True, style='mpl20', - tol=0.028 if platform.machine() == 'arm64' else 0) + 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() @@ -914,7 +930,7 @@ def test_legend_pathcollection_labelcolor_markeredgecolor_cmap(): # test the labelcolor for labelcolor='markeredgecolor' on PathCollection # with a colormap fig, ax = plt.subplots() - edgecolors = mpl.cm.viridis(np.random.rand(10)) + edgecolors = mpl.colormaps["viridis"](np.random.rand(10)) ax.scatter( np.arange(10), np.arange(10), @@ -969,13 +985,12 @@ def test_legend_pathcollection_labelcolor_markfacecolor_cmap(): # test the labelcolor for labelcolor='markerfacecolor' on PathCollection # with colormaps fig, ax = plt.subplots() - facecolors = mpl.cm.viridis(np.random.rand(10)) + colors = mpl.colormaps["viridis"](np.random.rand(10)) ax.scatter( np.arange(10), np.arange(10), label='#1', - c=np.arange(10), - facecolor=facecolors + c=colors ) leg = ax.legend(labelcolor='markerfacecolor') @@ -1062,6 +1077,201 @@ def test_legend_labelcolor_rcparam_markerfacecolor_short(): assert mpl.colors.same_color(text.get_color(), color) +def assert_last_legend_patch_color(histogram, leg, expected_color, + facecolor=False, edgecolor=False): + """ + Check that histogram color, legend handle color, and legend label color all + match the expected input. Provide facecolor and edgecolor flags to clarify + which feature to match. + """ + label_color = leg.texts[-1].get_color() + patch = leg.get_patches()[-1] + histogram = histogram[-1][0] + assert mpl.colors.same_color(label_color, expected_color) + if facecolor: + assert mpl.colors.same_color(label_color, patch.get_facecolor()) + assert mpl.colors.same_color(label_color, histogram.get_facecolor()) + if edgecolor: + assert mpl.colors.same_color(label_color, patch.get_edgecolor()) + assert mpl.colors.same_color(label_color, histogram.get_edgecolor()) + + +def test_legend_labelcolor_linecolor_histograms(): + x = np.arange(10) + + # testing c kwarg for bar, step, and stepfilled histograms + fig, ax = plt.subplots() + h = ax.hist(x, histtype='bar', color='r', label="red bar hist with a red label") + leg = ax.legend(labelcolor='linecolor') + assert_last_legend_patch_color(h, leg, 'r', facecolor=True) + + h = ax.hist(x, histtype='step', color='g', label="green step hist, green label") + leg = ax.legend(labelcolor='linecolor') + assert_last_legend_patch_color(h, leg, 'g', edgecolor=True) + + h = ax.hist(x, histtype='stepfilled', color='b', + label="blue stepfilled hist with a blue label") + leg = ax.legend(labelcolor='linecolor') + assert_last_legend_patch_color(h, leg, 'b', facecolor=True) + + # testing c, fc, and ec combinations for bar histograms + h = ax.hist(x, histtype='bar', color='r', ec='b', + label="red bar hist with blue edges and a red label") + leg = ax.legend(labelcolor='linecolor') + assert_last_legend_patch_color(h, leg, 'r', facecolor=True) + + h = ax.hist(x, histtype='bar', fc='r', ec='b', + label="red bar hist with blue edges and a red label") + leg = ax.legend(labelcolor='linecolor') + assert_last_legend_patch_color(h, leg, 'r', facecolor=True) + + h = ax.hist(x, histtype='bar', fc='none', ec='b', + label="unfilled blue bar hist with a blue label") + leg = ax.legend(labelcolor='linecolor') + assert_last_legend_patch_color(h, leg, 'b', edgecolor=True) + + # testing c, and ec combinations for step histograms + h = ax.hist(x, histtype='step', color='r', ec='b', + label="blue step hist with a blue label") + leg = ax.legend(labelcolor='linecolor') + assert_last_legend_patch_color(h, leg, 'b', edgecolor=True) + + h = ax.hist(x, histtype='step', ec='b', + label="blue step hist with a blue label") + leg = ax.legend(labelcolor='linecolor') + assert_last_legend_patch_color(h, leg, 'b', edgecolor=True) + + # testing c, fc, and ec combinations for stepfilled histograms + h = ax.hist(x, histtype='stepfilled', color='r', ec='b', + label="red stepfilled hist, blue edges, red label") + leg = ax.legend(labelcolor='linecolor') + assert_last_legend_patch_color(h, leg, 'r', facecolor=True) + + h = ax.hist(x, histtype='stepfilled', fc='r', ec='b', + label="red stepfilled hist, blue edges, red label") + leg = ax.legend(labelcolor='linecolor') + assert_last_legend_patch_color(h, leg, 'r', facecolor=True) + + h = ax.hist(x, histtype='stepfilled', fc='none', ec='b', + label="unfilled blue stepfilled hist, blue label") + leg = ax.legend(labelcolor='linecolor') + assert_last_legend_patch_color(h, leg, 'b', edgecolor=True) + + h = ax.hist(x, histtype='stepfilled', fc='r', ec='none', + label="edgeless red stepfilled hist with a red label") + leg = ax.legend(labelcolor='linecolor') + assert_last_legend_patch_color(h, leg, 'r', facecolor=True) + + +def assert_last_legend_linemarker_color(line_marker, leg, expected_color, color=False, + facecolor=False, edgecolor=False): + """ + Check that line marker color, legend handle color, and legend label color all + match the expected input. Provide color, facecolor and edgecolor flags to clarify + which feature to match. + """ + label_color = leg.texts[-1].get_color() + leg_marker = leg.get_lines()[-1] + assert mpl.colors.same_color(label_color, expected_color) + if color: + assert mpl.colors.same_color(label_color, leg_marker.get_color()) + assert mpl.colors.same_color(label_color, line_marker.get_color()) + if facecolor: + assert mpl.colors.same_color(label_color, leg_marker.get_markerfacecolor()) + assert mpl.colors.same_color(label_color, line_marker.get_markerfacecolor()) + if edgecolor: + assert mpl.colors.same_color(label_color, leg_marker.get_markeredgecolor()) + assert mpl.colors.same_color(label_color, line_marker.get_markeredgecolor()) + + +def test_legend_labelcolor_linecolor_plot(): + x = np.arange(5) + + # testing line plot + fig, ax = plt.subplots() + l, = ax.plot(x, c='r', label="red line with a red label") + leg = ax.legend(labelcolor='linecolor') + assert_last_legend_linemarker_color(l, leg, 'r', color=True) + + # testing c, fc, and ec combinations for maker plots + l, = ax.plot(x, 'o', c='r', label="red circles with a red label") + leg = ax.legend(labelcolor='linecolor') + assert_last_legend_linemarker_color(l, leg, 'r', color=True) + + l, = ax.plot(x, 'o', c='r', mec='b', label="red circles, blue edges, red label") + leg = ax.legend(labelcolor='linecolor') + assert_last_legend_linemarker_color(l, leg, 'r', color=True) + + l, = ax.plot(x, 'o', mfc='r', mec='b', label="red circles, blue edges, red label") + leg = ax.legend(labelcolor='linecolor') + assert_last_legend_linemarker_color(l, leg, 'r', facecolor=True) + + # 'none' cases + l, = ax.plot(x, 'o', mfc='none', mec='b', + label="blue unfilled circles, blue label") + leg = ax.legend(labelcolor='linecolor') + assert_last_legend_linemarker_color(l, leg, 'b', edgecolor=True) + + l, = ax.plot(x, 'o', mfc='r', mec='none', label="red edgeless circles, red label") + leg = ax.legend(labelcolor='linecolor') + assert_last_legend_linemarker_color(l, leg, 'r', facecolor=True) + + l, = ax.plot(x, 'o', c='none', mec='none', + label="black label despite invisible circles for dummy entries") + leg = ax.legend(labelcolor='linecolor') + assert_last_legend_linemarker_color(l, leg, 'k') + + +def assert_last_legend_scattermarker_color(scatter_marker, leg, expected_color, + facecolor=False, edgecolor=False): + """ + Check that scatter marker color, legend handle color, and legend label color all + match the expected input. Provide facecolor and edgecolor flags to clarify + which feature to match. + """ + label_color = leg.texts[-1].get_color() + leg_handle = leg.legend_handles[-1] + assert mpl.colors.same_color(label_color, expected_color) + if facecolor: + assert mpl.colors.same_color(label_color, leg_handle.get_facecolor()) + assert mpl.colors.same_color(label_color, scatter_marker.get_facecolor()) + if edgecolor: + assert mpl.colors.same_color(label_color, leg_handle.get_edgecolor()) + assert mpl.colors.same_color(label_color, scatter_marker.get_edgecolor()) + + +def test_legend_labelcolor_linecolor_scatter(): + x = np.arange(5) + + # testing c, fc, and ec combinations for scatter plots + fig, ax = plt.subplots() + s = ax.scatter(x, x, c='r', label="red circles with a red label") + leg = ax.legend(labelcolor='linecolor') + assert_last_legend_scattermarker_color(s, leg, 'r', facecolor=True) + + s = ax.scatter(x, x, c='r', ec='b', label="red circles, blue edges, red label") + leg = ax.legend(labelcolor='linecolor') + assert_last_legend_scattermarker_color(s, leg, 'r', facecolor=True) + + s = ax.scatter(x, x, fc='r', ec='b', label="red circles, blue edges, red label") + leg = ax.legend(labelcolor='linecolor') + assert_last_legend_scattermarker_color(s, leg, 'r', facecolor=True) + + # 'none' cases + s = ax.scatter(x, x, fc='none', ec='b', label="blue unfilled circles, blue label") + leg = ax.legend(labelcolor='linecolor') + assert_last_legend_scattermarker_color(s, leg, 'b', edgecolor=True) + + s = ax.scatter(x, x, fc='r', ec='none', label="red edgeless circles, red label") + leg = ax.legend(labelcolor='linecolor') + assert_last_legend_scattermarker_color(s, leg, 'r', facecolor=True) + + s = ax.scatter(x, x, c='none', ec='none', + label="black label despite invisible circles for dummy entries") + leg = ax.legend(labelcolor='linecolor') + assert_last_legend_scattermarker_color(s, leg, 'k') + + @pytest.mark.filterwarnings("ignore:No artists with labels found to put in legend") def test_get_set_draggable(): legend = plt.legend() @@ -1178,21 +1388,15 @@ def test_plot_multiple_input_single_label(label): assert legend_texts == [str(label)] * 2 -@pytest.mark.parametrize('label_array', [['low', 'high'], - ('low', 'high'), - np.array(['low', 'high'])]) -def test_plot_single_input_multiple_label(label_array): +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.warns(mpl.MatplotlibDeprecationWarning, - match='Passing label as a length 2 sequence'): - ax.plot(x, y, label=label_array) - leg = ax.legend() - assert len(leg.get_texts()) == 1 - assert leg.get_texts()[0].get_text() == str(label_array) + 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(): @@ -1427,6 +1631,21 @@ def test_legend_text(): 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) @@ -1457,3 +1676,86 @@ def test_boxplot_legend_labels(): 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:]) + + +def test_legend_linewidth(): + """Test legend.linewidth parameter and rcParam.""" + fig, ax = plt.subplots() + ax.plot([1, 2, 3], label='data') + + # Test direct parameter + leg = ax.legend(linewidth=2.5) + assert leg.legendPatch.get_linewidth() == 2.5 + + # Test rcParam + with mpl.rc_context({'legend.linewidth': 3.0}): + fig, ax = plt.subplots() + ax.plot([1, 2, 3], label='data') + leg = ax.legend() + assert leg.legendPatch.get_linewidth() == 3.0 + + # Test None default (should inherit from patch.linewidth) + with mpl.rc_context({'legend.linewidth': None, 'patch.linewidth': 1.5}): + fig, ax = plt.subplots() + ax.plot([1, 2, 3], label='data') + leg = ax.legend() + assert leg.legendPatch.get_linewidth() == 1.5 + + # Test that direct parameter overrides rcParam + with mpl.rc_context({'legend.linewidth': 1.0}): + fig, ax = plt.subplots() + ax.plot([1, 2, 3], label='data') + leg = ax.legend(linewidth=4.0) + assert leg.legendPatch.get_linewidth() == 4.0 + + +def test_patchcollection_legend(): + # Test that PatchCollection labels show up in legend and preserve visual + # properties (issue #23998) + fig, ax = plt.subplots() + + pc = mcollections.PatchCollection( + [mpatches.Circle((0, 0), 1), mpatches.Circle((2, 0), 1)], + label="patch collection", + facecolor='red', + edgecolor='blue', + linewidths=3, + linestyle='--', + ) + ax.add_collection(pc) + ax.autoscale_view() + + leg = ax.legend() + + # Check that the legend contains our label + assert len(leg.get_texts()) == 1 + assert leg.get_texts()[0].get_text() == "patch collection" + + # Check that the legend handle exists and has correct visual properties + assert len(leg.legend_handles) == 1 + legend_patch = leg.legend_handles[0] + assert mpl.colors.same_color(legend_patch.get_facecolor(), + pc.get_facecolor()[0]) + assert mpl.colors.same_color(legend_patch.get_edgecolor(), + pc.get_edgecolor()[0]) + assert legend_patch.get_linewidth() == pc.get_linewidths()[0] + assert legend_patch.get_linestyle() == pc.get_linestyles()[0] + + +def test_patchcollection_legend_empty(): + # Test that empty PatchCollection doesn't crash + fig, ax = plt.subplots() + + # Create an empty PatchCollection + pc = mcollections.PatchCollection([], label="empty collection") + ax.add_collection(pc) + + # This should not crash + leg = ax.legend() + + # Check that the label still appears + assert len(leg.get_texts()) == 1 + assert leg.get_texts()[0].get_text() == "empty collection" + + # The legend handle should exist + assert len(leg.legend_handles) == 1 diff --git a/lib/matplotlib/tests/test_lines.py b/lib/matplotlib/tests/test_lines.py index ee8b5b4aaa9e..e2a63bbf962b 100644 --- a/lib/matplotlib/tests/test_lines.py +++ b/lib/matplotlib/tests/test_lines.py @@ -4,7 +4,6 @@ import itertools import platform -import timeit from types import SimpleNamespace from cycler import cycler @@ -31,52 +30,6 @@ def test_segment_hits(): assert_array_equal(mlines.segment_hits(cx, cy, x, y, radius), [0]) -# Runtimes on a loaded system are inherently flaky. Not so much that a rerun -# won't help, hopefully. -@pytest.mark.flaky(reruns=3) -def test_invisible_Line_rendering(): - """ - GitHub issue #1256 identified a bug in Line.draw method - - Despite visibility attribute set to False, the draw method was not - returning early enough and some pre-rendering code was executed - though not necessary. - - Consequence was an excessive draw time for invisible Line instances - holding a large number of points (Npts> 10**6) - """ - # Creates big x and y data: - N = 10**7 - x = np.linspace(0, 1, N) - y = np.random.normal(size=N) - - # Create a plot figure: - fig = plt.figure() - ax = plt.subplot() - - # Create a "big" Line instance: - l = mlines.Line2D(x, y) - l.set_visible(False) - # but don't add it to the Axis instance `ax` - - # [here Interactive panning and zooming is pretty responsive] - # Time the canvas drawing: - t_no_line = min(timeit.repeat(fig.canvas.draw, number=1, repeat=3)) - # (gives about 25 ms) - - # Add the big invisible Line: - ax.add_line(l) - - # [Now interactive panning and zooming is very slow] - # Time the canvas drawing: - t_invisible_line = min(timeit.repeat(fig.canvas.draw, number=1, repeat=3)) - # gives about 290 ms for N = 10**7 pts - - slowdown_factor = t_invisible_line / t_no_line - slowdown_threshold = 2 # trying to avoid false positive failures - assert slowdown_factor < slowdown_threshold - - def test_set_line_coll_dash(): fig, ax = plt.subplots() np.random.seed(0) @@ -139,8 +92,15 @@ def test_valid_linestyles(): line.set_linestyle('aardvark') +@mpl.style.context('mpl20') +def test_zero_linewidth_dashed_uses_solid_gc_dashes(): + fig, ax = plt.subplots() + ax.plot([0, 1], [0, 1], ls='--', lw=0) + fig.draw_without_rendering() + + @image_comparison(['drawstyle_variants.png'], remove_text=True, - tol=0.03 if platform.machine() == 'arm64' else 0) + 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] @@ -153,7 +113,7 @@ def test_drawstyle_variants(): ax.set(xlim=(0, 2), ylim=(0, 2)) -@check_figures_equal(extensions=('png',)) +@check_figures_equal() def test_no_subslice_with_transform(fig_ref, fig_test): ax = fig_ref.add_subplot() x = np.arange(2000) @@ -183,9 +143,8 @@ def test_set_drawstyle(): assert len(line.get_path().vertices) == len(x) -@image_comparison( - ['line_collection_dashes'], remove_text=True, style='mpl20', - tol=0 if platform.machine() == 'x86_64' else 0.65) +@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, ax = plt.subplots() np.random.seed(0) @@ -220,8 +179,8 @@ def test_marker_fill_styles(): markeredgecolor=color, markeredgewidth=2) - ax.set_ylim([0, 7.5]) - ax.set_xlim([-5, 155]) + ax.set_ylim(0, 7.5) + ax.set_xlim(-5, 155) def test_markerfacecolor_fillstyle(): @@ -260,7 +219,7 @@ def test_step_markers(fig_test, fig_ref): @pytest.mark.parametrize("parent", ["figure", "axes"]) -@check_figures_equal(extensions=('png',)) +@check_figures_equal() def test_markevery(fig_test, fig_ref, parent): np.random.seed(42) x = np.linspace(0, 1, 14) @@ -333,13 +292,13 @@ def test_marker_as_markerstyle(): @image_comparison(['striped_line.png'], remove_text=True, style='mpl20') -def test_striped_lines(): +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=' ') + 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=' ', alpha=0.5) + linestyle=(0, (2, 5, 4, 2)), lw=5, label='black in red', alpha=0.5) ax.legend(handlelength=5) @@ -386,7 +345,7 @@ def test_input_copy(fig_test, fig_ref): fig_ref.add_subplot().plot([0, 2, 4], [0, 2, 4], ".-", drawstyle="steps") -@check_figures_equal(extensions=["png"]) +@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], diff --git a/lib/matplotlib/tests/test_marker.py b/lib/matplotlib/tests/test_marker.py index 463ff1d05c96..171d06fd3d93 100644 --- a/lib/matplotlib/tests/test_marker.py +++ b/lib/matplotlib/tests/test_marker.py @@ -22,6 +22,7 @@ def test_marker_fillstyle(): r'$\frac{1}{2}$', "$\u266B$", 1, + np.int64(1), markers.TICKLEFT, [[-1, 0], [1, 0]], np.array([[-1, 0], [1, 0]]), @@ -63,7 +64,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() @@ -123,7 +124,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() @@ -159,7 +160,7 @@ def draw_ref_marker(y, style, size): # 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(extensions=['png'], tol=1.86) +@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() @@ -168,7 +169,7 @@ def test_text_marker(fig_ref, fig_test): ax_test.plot(0, 0, marker=r'$\bullet$', markersize=100, markeredgewidth=0) -@check_figures_equal() +@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 @@ -181,9 +182,9 @@ def test_marker_clipping(fig_ref, fig_test): width = 2 * marker_size * ncol height = 2 * marker_size * nrow * 2 fig_ref.set_size_inches((width / fig_ref.dpi, height / fig_ref.dpi)) - ax_ref = fig_ref.add_axes([0, 0, 1, 1]) + ax_ref = fig_ref.add_axes((0, 0, 1, 1)) fig_test.set_size_inches((width / fig_test.dpi, height / fig_ref.dpi)) - ax_test = fig_test.add_axes([0, 0, 1, 1]) + ax_test = fig_test.add_axes((0, 0, 1, 1)) for i, marker in enumerate(markers.MarkerStyle.markers): x = i % ncol diff --git a/lib/matplotlib/tests/test_mathtext.py b/lib/matplotlib/tests/test_mathtext.py index 4dcd08ba0718..39c28dc9228c 100644 --- a/lib/matplotlib/tests/test_mathtext.py +++ b/lib/matplotlib/tests/test_mathtext.py @@ -4,8 +4,9 @@ from pathlib import Path import platform import re -from xml.etree import ElementTree as ET +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 @@ -16,7 +17,8 @@ import matplotlib as mpl from matplotlib.testing.decorators import check_figures_equal, image_comparison import matplotlib.pyplot as plt -from matplotlib import mathtext, _mathtext +from matplotlib import font_manager as fm, mathtext, _mathtext +from matplotlib.ft2font import LoadFlags pyparsing_version = parse_version(pyparsing.__version__) @@ -264,7 +266,7 @@ def test_mathfont_rendering(baseline_images, fontset, index, text): horizontalalignment='center', verticalalignment='center') -@check_figures_equal(extensions=["png"]) +@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] @@ -373,13 +375,13 @@ def test_single_minus_sign(): 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.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)$") @@ -402,13 +404,13 @@ def test_operator_space(fig_test, fig_ref): fig_ref.text(0.1, 0.9, r"$\mathrm{sin}^2 \mathrm{\,cos}$") -@check_figures_equal(extensions=["png"]) +@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(extensions=["png"]) +@check_figures_equal() def test_genfrac_displaystyle(fig_test, fig_ref): fig_test.text(0.1, 0.1, r"$\dfrac{2x}{3y}$") @@ -435,7 +437,7 @@ def test_mathtext_fallback_invalid(): ("stix", ['DejaVu Sans', 'mpltest', 'STIXGeneral', 'STIXGeneral', 'STIXGeneral'])]) def test_mathtext_fallback(fallback, fontlist): mpl.font_manager.fontManager.addfont( - str(Path(__file__).resolve().parent / 'mpltest.ttf')) + (Path(__file__).resolve().parent / 'data/mpltest.ttf')) mpl.rcParams["svg.fonttype"] = 'none' mpl.rcParams['mathtext.fontset'] = 'custom' mpl.rcParams['mathtext.rm'] = 'mpltest' @@ -554,7 +556,45 @@ def test_mathtext_operators(): fig.draw_without_rendering() -@check_figures_equal(extensions=["png"]) +@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 37b41fafdb78..6dab056f9170 100644 --- a/lib/matplotlib/tests/test_matplotlib.py +++ b/lib/matplotlib/tests/test_matplotlib.py @@ -1,6 +1,7 @@ import os import subprocess import sys +from unittest.mock import patch import pytest @@ -18,9 +19,9 @@ 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, +@pytest.mark.skipif(sys.platform not in ["linux", "darwin"], + reason="chmod() doesn't work on this platform") +@pytest.mark.skipif(sys.platform in ["linux", "darwin"] and os.geteuid() == 0, reason="chmod() doesn't work as root") def test_tmpconfigdir_warning(tmp_path): """Test that a warning is emitted if a temporary configdir must be used.""" @@ -80,3 +81,62 @@ def test_importable_with__OO(): [sys.executable, "-OO", "-c", program], env={**os.environ, "MPLBACKEND": ""}, check=True ) + + +@patch('matplotlib.subprocess.check_output') +def test_get_executable_info_timeout(mock_check_output): + """ + Test that _get_executable_info raises ExecutableNotFoundError if the + command times out. + """ + + mock_check_output.side_effect = subprocess.TimeoutExpired(cmd=['mock'], timeout=30) + + with pytest.raises(matplotlib.ExecutableNotFoundError, match='Timed out'): + matplotlib._get_executable_info.__wrapped__('inkscape') + + +@pytest.mark.skipif(sys.platform != "win32", reason="Windows-specific test") +def test_configdir_uses_localappdata_on_windows(tmp_path): + """Test that on Windows, config/cache dir uses LOCALAPPDATA for fresh installs.""" + localappdata = tmp_path / "AppData/Local" + localappdata.mkdir(parents=True) + # Set USERPROFILE to tmp_path so the old location check finds nothing + fake_home = tmp_path / "home" + fake_home.mkdir() + + proc = subprocess_run_for_testing( + [sys.executable, "-c", + "import matplotlib; print(matplotlib.get_configdir())"], + env={**os.environ, "LOCALAPPDATA": str(localappdata), + "USERPROFILE": str(fake_home), "MPLCONFIGDIR": ""}, + capture_output=True, text=True, check=True) + + configdir = proc.stdout.strip() + # On Windows with no existing old config, should use LOCALAPPDATA\matplotlib + assert configdir == str(localappdata / "matplotlib") + + +@pytest.mark.skipif(sys.platform != "win32", reason="Windows-specific test") +def test_configdir_uses_userprofile_on_windows_if_exists(tmp_path): + """ + Test that on Windows, config/cache dir uses %USERPROFILE% if .matplotlib + exists. + """ + localappdata = tmp_path / "AppData/Local" + localappdata.mkdir(parents=True) + fake_home = tmp_path / "home" + fake_home.mkdir() + old_configdir = fake_home / ".matplotlib" + old_configdir.mkdir() + + proc = subprocess_run_for_testing( + [sys.executable, "-c", + "import matplotlib; print(matplotlib.get_configdir())"], + env={**os.environ, "LOCALAPPDATA": str(localappdata), + "USERPROFILE": str(fake_home), "MPLCONFIGDIR": ""}, + capture_output=True, text=True, check=True) + + configdir = proc.stdout.strip() + # On Windows with existing old config, should continue using it + assert configdir == str(old_configdir) diff --git a/lib/matplotlib/tests/test_mlab.py b/lib/matplotlib/tests/test_mlab.py index 3b0d2529b5f1..82f877b4cc01 100644 --- a/lib/matplotlib/tests/test_mlab.py +++ b/lib/matplotlib/tests/test_mlab.py @@ -1,3 +1,5 @@ +import sys + from numpy.testing import (assert_allclose, assert_almost_equal, assert_array_equal, assert_array_almost_equal_nulp) import numpy as np @@ -429,7 +431,16 @@ def test_spectral_helper_psd(self, mode, case): assert spec.shape[0] == freqs.shape[0] assert spec.shape[1] == getattr(self, f"t_{case}").shape[0] - def test_csd(self): + @pytest.mark.parametrize('bitsize', [ + pytest.param(None, id='default'), + pytest.param(32, + marks=pytest.mark.skipif(sys.maxsize <= 2**32, + reason='System is already 32-bit'), + id='32-bit') + ]) + def test_csd(self, bitsize, monkeypatch): + if bitsize is not None: + monkeypatch.setattr(sys, 'maxsize', 2**bitsize) freqs = self.freqs_density spec, fsp = mlab.csd(x=self.y, y=self.y+1, NFFT=self.NFFT_density, @@ -873,6 +884,8 @@ def test_single_dataset_element(self): with pytest.raises(ValueError): mlab.GaussianKDE([42]) + @pytest.mark.skipif(sys.platform == 'emscripten', + reason="WASM doesn't support floating-point exceptions") def test_silverman_multidim_dataset(self): """Test silverman's for a multi-dimensional array.""" x1 = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) @@ -886,6 +899,8 @@ def test_silverman_singledim_dataset(self): y_expected = 0.76770389927475502 assert_almost_equal(mygauss.covariance_factor(), y_expected, 7) + @pytest.mark.skipif(sys.platform == 'emscripten', + reason="WASM doesn't support floating-point exceptions") def test_scott_multidim_dataset(self): """Test scott's output for a multi-dimensional array.""" x1 = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) diff --git a/lib/matplotlib/tests/test_multivariate_colormaps.py b/lib/matplotlib/tests/test_multivariate_colormaps.py index 81a2e6adeb35..592058212a24 100644 --- a/lib/matplotlib/tests/test_multivariate_colormaps.py +++ b/lib/matplotlib/tests/test_multivariate_colormaps.py @@ -212,9 +212,26 @@ def test_multivar_resample(): 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) + assert_allclose(cmap((1.0, 1.0)), (1, 1, 1, 1)) + assert_allclose(cmap((0.0, 0.0)), (0, 0, 0, 1)) + assert_allclose(cmap((0.2, 0.8)), (0.2, 0.5, 0.8, 1)) + assert_allclose(cmap((0.0, 0.0), alpha=0.1), (0, 0, 0, 0.1)) + + +def test_bivar_cmap_lut_smooth(): + cmap = mpl.bivar_colormaps['BiOrangeBlue'] + + assert_allclose(cmap.lut[:, 0, 0], np.linspace(0, 1, 256)) + assert_allclose(cmap.lut[:, 255, 0], np.linspace(0, 1, 256)) + assert_allclose(cmap.lut[:, 0, 1], np.linspace(0, 0.5, 256)) + assert_allclose(cmap.lut[:, 153, 1], np.linspace(0.3, 0.8, 256)) + assert_allclose(cmap.lut[:, 255, 1], np.linspace(0.5, 1, 256)) + + assert_allclose(cmap.lut[0, :, 1], np.linspace(0, 0.5, 256)) + assert_allclose(cmap.lut[102, :, 1], np.linspace(0.2, 0.7, 256)) + assert_allclose(cmap.lut[255, :, 1], np.linspace(0.5, 1, 256)) + assert_allclose(cmap.lut[0, :, 2], np.linspace(0, 1, 256)) + assert_allclose(cmap.lut[255, :, 2], np.linspace(0, 1, 256)) def test_bivar_cmap_call(): @@ -312,20 +329,36 @@ def test_bivar_cmap_call(): 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)) + +def test_bivar_cmap_1d_origin(): + """ + Test getting 1D colormaps with different origins + """ + cmap0 = mpl.bivar_colormaps['BiOrangeBlue'] + assert_allclose(cmap0[0].colors[:, 0], np.linspace(0, 1, 256)) + assert_allclose(cmap0[0].colors[:, 1], np.linspace(0, 0.5, 256)) + assert_allclose(cmap0[0].colors[:, 2], 0) + assert_allclose(cmap0[1].colors[:, 0], 0) + assert_allclose(cmap0[1].colors[:, 1], np.linspace(0, 0.5, 256)) + assert_allclose(cmap0[1].colors[:, 2], np.linspace(0, 1, 256)) + + cmap1 = cmap0.with_extremes(origin=(0, 1)) + assert_allclose(cmap1[0].colors[:, 0], np.linspace(0, 1, 256)) + assert_allclose(cmap1[0].colors[:, 1], np.linspace(0.5, 1, 256)) + assert_allclose(cmap1[0].colors[:, 2], 1) + assert_allclose(cmap1[1].colors, cmap0[1].colors) + + cmap2 = cmap0.with_extremes(origin=(0.2, 0.4)) + assert_allclose(cmap2[0].colors[:, 0], np.linspace(0, 1, 256)) + assert_allclose(cmap2[0].colors[:, 1], np.linspace(0.2, 0.7, 256)) + assert_allclose(cmap2[0].colors[:, 2], 0.4) + assert_allclose(cmap2[1].colors[:, 0], 0.2) + assert_allclose(cmap2[1].colors[:, 1], np.linspace(0.1, 0.6, 256)) + assert_allclose(cmap2[1].colors[:, 2], np.linspace(0, 1, 256)) + with pytest.raises(KeyError, match="only 0 or 1 are valid keys"): - cs = cmap[2] + cs = cmap0[2] def test_bivar_getitem(): @@ -433,22 +466,18 @@ def test_bivar_cmap_from_image(): 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'] - cmap = mpl.bivar_colormaps['BiOrangeBlue'].resampled((2, -2)) - assert_allclose(cmap((0.25, 0.25)), (0., 0.5, 1., 1.), atol=1e-2) + assert_allclose(cmap.resampled((2, 2))((0.25, 0.25)), (0, 0, 0, 1)) + assert_allclose(cmap.resampled((-2, 2))((0.25, 0.25)), (1., 0.5, 0., 1.)) + assert_allclose(cmap.resampled((2, -2))((0.25, 0.25)), (0., 0.5, 1., 1.)) + assert_allclose(cmap.resampled((-2, -2))((0.25, 0.25)), (1, 1, 1, 1)) - cmap = mpl.bivar_colormaps['BiOrangeBlue'].resampled((-2, -2)) - assert_allclose(cmap((0.25, 0.25)), (1, 1, 1, 1), atol=1e-2) + assert_allclose(cmap((0.8, 0.4)), (0.8, 0.6, 0.4, 1.)) + assert_allclose(cmap.reversed()((1 - 0.8, 1 - 0.4)), (0.8, 0.6, 0.4, 1.)) - 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) + assert_allclose(cmap((0.6, 0.2)), (0.6, 0.4, 0.2, 1.)) + assert_allclose(cmap.transposed()((0.2, 0.6)), (0.6, 0.4, 0.2, 1.)) with pytest.raises(ValueError, match="lutshape must be of length"): cmap = cmap.resampled(4) diff --git a/lib/matplotlib/tests/test_offsetbox.py b/lib/matplotlib/tests/test_offsetbox.py index f18fa7c777d1..f126b1cbb466 100644 --- a/lib/matplotlib/tests/test_offsetbox.py +++ b/lib/matplotlib/tests/test_offsetbox.py @@ -48,8 +48,8 @@ def test_offsetbox_clipping(): da.add_artist(bg) da.add_artist(line) ax.add_artist(anchored_box) - ax.set_xlim((0, 1)) - ax.set_ylim((0, 1)) + ax.set_xlim(0, 1) + ax.set_ylim(0, 1) def test_offsetbox_clip_children(): @@ -455,8 +455,55 @@ def test_remove_draggable(): 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 = 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 + + +def test_anchored_offsetbox_tuple_and_float_borderpad(): + """ + Test AnchoredOffsetbox correctly handles both float and tuple for borderpad. + """ + + fig, ax = plt.subplots() + + # Case 1: Establish a baseline with float value + text_float = AnchoredText("float", loc='lower left', borderpad=5) + ax.add_artist(text_float) + + # Case 2: Test that a symmetric tuple gives the exact same result. + text_tuple_equal = AnchoredText("tuple", loc='lower left', borderpad=(5, 5)) + ax.add_artist(text_tuple_equal) + + # Case 3: Test that an asymmetric tuple with different values works as expected. + text_tuple_asym = AnchoredText("tuple_asym", loc='lower left', borderpad=(10, 4)) + ax.add_artist(text_tuple_asym) + + # Draw the canvas to calculate final positions + fig.canvas.draw() + + pos_float = text_float.get_window_extent() + pos_tuple_equal = text_tuple_equal.get_window_extent() + pos_tuple_asym = text_tuple_asym.get_window_extent() + + # Assertion 1: Prove that borderpad=5 is identical to borderpad=(5, 5). + assert pos_tuple_equal.x0 == pos_float.x0 + assert pos_tuple_equal.y0 == pos_float.y0 + + # Assertion 2: Prove that the asymmetric padding moved the box + # further from the origin than the baseline in the x-direction and less far + # in the y-direction. + assert pos_tuple_asym.x0 > pos_float.x0 + assert pos_tuple_asym.y0 < pos_float.y0 diff --git a/lib/matplotlib/tests/test_patches.py b/lib/matplotlib/tests/test_patches.py index 3544ce8cb10c..80dcc43894c4 100644 --- a/lib/matplotlib/tests/test_patches.py +++ b/lib/matplotlib/tests/test_patches.py @@ -178,7 +178,7 @@ def test_rotate_rect(): assert_almost_equal(rect1.get_verts(), new_verts) -@check_figures_equal(extensions=['png']) +@check_figures_equal() def test_rotate_rect_draw(fig_test, fig_ref): ax_test = fig_test.add_subplot() ax_ref = fig_ref.add_subplot() @@ -199,7 +199,7 @@ def test_rotate_rect_draw(fig_test, fig_ref): assert rect_test.get_angle() == angle -@check_figures_equal(extensions=['png']) +@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() @@ -241,7 +241,7 @@ 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, ax = plt.subplots() ax.set_xlim([-18, 20]) @@ -395,7 +395,7 @@ def test_patch_linestyle_accents(): fig.canvas.draw() -@check_figures_equal(extensions=['png']) +@check_figures_equal() def test_patch_linestyle_none(fig_test, fig_ref): circle = mpath.Path.unit_circle() @@ -438,7 +438,7 @@ def test_wedge_movement(): @image_comparison(['wedge_range'], remove_text=True, - tol=0.009 if platform.machine() == 'arm64' else 0) + tol=0 if platform.machine() == 'x86_64' else 0.009) def test_wedge_range(): ax = plt.axes() @@ -564,7 +564,7 @@ def test_units_rectangle(): @image_comparison(['connection_patch.png'], style='mpl20', remove_text=True, - tol=0.024 if platform.machine() == 'arm64' else 0) + tol=0 if platform.machine() == 'x86_64' else 0.024) def test_connection_patch(): fig, (ax1, ax2) = plt.subplots(1, 2) @@ -583,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 @@ -606,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 @@ -656,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]) @@ -791,7 +813,7 @@ def test_boxstyle_errors(fmt, match): BoxStyle(fmt) -@image_comparison(baseline_images=['annulus'], extensions=['png']) +@image_comparison(['annulus.png']) def test_annulus(): fig, ax = plt.subplots() @@ -803,7 +825,7 @@ def test_annulus(): ax.set_aspect('equal') -@image_comparison(baseline_images=['annulus'], extensions=['png']) +@image_comparison(['annulus.png']) def test_annulus_setters(): fig, ax = plt.subplots() @@ -824,7 +846,7 @@ def test_annulus_setters(): ell.angle = 45 -@image_comparison(baseline_images=['annulus'], extensions=['png']) +@image_comparison(['annulus.png']) def test_annulus_setters2(): fig, ax = plt.subplots() @@ -885,6 +907,14 @@ def test_default_linestyle(): assert patch.get_linestyle() == 'solid' +@mpl.style.context('mpl20') +def test_patch_zero_linewidth_dashed_uses_solid_gc_dashes(): + fig, ax = plt.subplots() + ax.add_patch(Rectangle( + (0, 0), 1, 1, fill=False, linewidth=0, linestyle='--')) + fig.draw_without_rendering() + + def test_default_capstyle(): patch = Patch() assert patch.get_capstyle() == 'butt' @@ -919,7 +949,9 @@ def test_arc_in_collection(fig_test, fig_ref): 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) + ax_ref = fig_ref.subplots() + ax_ref.add_patch(arc1) + ax_ref.autoscale_view() fig_test.subplots().add_collection(col) @@ -960,3 +992,198 @@ def test_arrow_set_data(): ) 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']) + + +def test_empty_fancyarrow(): + fig, ax = plt.subplots() + arrow = ax.arrow([], [], [], []) + assert arrow is not None + + +def test_patch_edgegapcolor_getter_setter(): + """Test that edgegapcolor can be set and retrieved.""" + patch = Rectangle((0, 0), 1, 1) + # Default is None + assert patch.get_edgegapcolor() is None + + # Set to a color + patch.set_edgegapcolor('red') + assert mcolors.same_color(patch.get_edgegapcolor(), 'red') + + # Set back to None + patch.set_edgegapcolor(None) + assert patch.get_edgegapcolor() is None + + +def test_patch_edgegapcolor_init(): + """Test that edgegapcolor can be passed in __init__.""" + patch = Rectangle((0, 0), 1, 1, edgegapcolor='blue') + assert mcolors.same_color(patch.get_edgegapcolor(), 'blue') + + +def test_patch_has_dashed_edge(): + """Test _has_dashed_edge method for patches.""" + patch = Rectangle((0, 0), 1, 1) + patch.set_linestyle('solid') + assert not patch._has_dashed_edge() + + patch.set_linestyle('--') + assert patch._has_dashed_edge() + + patch.set_linestyle(':') + assert patch._has_dashed_edge() + + patch.set_linestyle('-.') + assert patch._has_dashed_edge() + + # Test custom linestyle + patch.set_linestyle((0, (2, 2, 10, 2))) + assert patch._has_dashed_edge() + + +def test_patch_edgegapcolor_update_from(): + """Test that edgegapcolor is copied in update_from.""" + patch1 = Rectangle((0, 0), 1, 1, edgegapcolor='green') + patch2 = Rectangle((1, 1), 2, 2) + + patch2.update_from(patch1) + assert mcolors.same_color(patch2.get_edgegapcolor(), 'green') + + +@image_comparison(['patch_edgegapcolor.png'], remove_text=True, style='mpl20') +def test_patch_edgegapcolor_visual(): + """Visual test for patch edgegapcolor (striped edges).""" + fig, ax = plt.subplots() + + # Rectangle with edgegapcolor + rect = Rectangle((0.1, 0.1), 0.3, 0.3, fill=False, + edgecolor='blue', edgegapcolor='orange', + linestyle='--', linewidth=3) + ax.add_patch(rect) + + # Ellipse with edgegapcolor + ellipse = Ellipse((0.7, 0.3), 0.3, 0.2, fill=False, + edgecolor='red', edgegapcolor='yellow', + linestyle=':', linewidth=3) + ax.add_patch(ellipse) + + # Polygon with edgegapcolor + polygon = Polygon([[0.1, 0.6], [0.3, 0.9], [0.4, 0.6]], fill=False, + edgecolor='green', edgegapcolor='purple', + linestyle='-.', linewidth=3) + ax.add_patch(polygon) + + ax.set_xlim(0, 1) + ax.set_ylim(0, 1) + ax.set_aspect('equal') diff --git a/lib/matplotlib/tests/test_path.py b/lib/matplotlib/tests/test_path.py index 2c4df6ea3b39..a61f01c0d48a 100644 --- a/lib/matplotlib/tests/test_path.py +++ b/lib/matplotlib/tests/test_path.py @@ -151,12 +151,12 @@ def test_nonlinear_containment(): @image_comparison(['arrow_contains_point.png'], remove_text=True, style='mpl20', - tol=0.027 if platform.machine() == 'arm64' else 0) + tol=0 if platform.machine() == 'x86_64' else 0.027) def test_arrow_contains_point(): # fix bug (#8384) fig, ax = plt.subplots() - ax.set_xlim((0, 2)) - ax.set_ylim((0, 2)) + ax.set_xlim(0, 2) + ax.set_ylim(0, 2) # create an arrow with Curve style arrow = patches.FancyArrowPatch((0.5, 0.25), (1.5, 0.75), @@ -283,7 +283,7 @@ def test_marker_paths_pdf(): @image_comparison(['nan_path'], style='default', remove_text=True, extensions=['pdf', 'svg', 'eps', 'png'], - tol=0.009 if platform.machine() == 'arm64' else 0) + 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] @@ -355,15 +355,49 @@ def test_path_deepcopy(): # Should not raise any error verts = [[0, 0], [1, 1]] codes = [Path.MOVETO, Path.LINETO] - path1 = Path(verts) - path2 = Path(verts, codes) + path1 = Path(verts, readonly=True) + path2 = Path(verts, codes, readonly=True) path1_copy = path1.deepcopy() path2_copy = path2.deepcopy() assert path1 is not path1_copy assert path1.vertices is not path1_copy.vertices + assert_array_equal(path1.vertices, path1_copy.vertices) + assert path1.readonly + assert not path1_copy.readonly assert path2 is not path2_copy assert path2.vertices is not path2_copy.vertices + assert_array_equal(path2.vertices, path2_copy.vertices) assert path2.codes is not path2_copy.codes + assert_array_equal(path2.codes, path2_copy.codes) + assert path2.readonly + assert not path2_copy.readonly + + +def test_path_deepcopy_cycle(): + class PathWithCycle(Path): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.x = self + + p = PathWithCycle([[0, 0], [1, 1]], readonly=True) + p_copy = p.deepcopy() + assert p_copy is not p + assert p.readonly + assert not p_copy.readonly + assert p_copy.x is p_copy + + class PathWithCycle2(Path): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.x = [self] * 2 + + p2 = PathWithCycle2([[0, 0], [1, 1]], readonly=True) + p2_copy = p2.deepcopy() + assert p2_copy is not p2 + assert p2.readonly + assert not p2_copy.readonly + assert p2_copy.x[0] is p2_copy + assert p2_copy.x[1] is p2_copy def test_path_shallowcopy(): @@ -541,3 +575,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 bf067b2abbfd..d957ef2a5510 100644 --- a/lib/matplotlib/tests/test_patheffects.py +++ b/lib/matplotlib/tests/test_patheffects.py @@ -2,7 +2,7 @@ import numpy as np -from matplotlib.testing.decorators import image_comparison +from matplotlib.testing.decorators import image_comparison, check_figures_equal import matplotlib.pyplot as plt import matplotlib.patheffects as path_effects from matplotlib.path import Path @@ -30,7 +30,7 @@ def test_patheffect1(): @image_comparison(['patheffect2'], remove_text=True, style='mpl20', - tol=0.06 if platform.machine() == 'arm64' else 0) + tol=0 if platform.machine() == 'x86_64' else 0.06) def test_patheffect2(): ax2 = plt.subplot() @@ -45,7 +45,8 @@ def test_patheffect2(): foreground="w")]) -@image_comparison(['patheffect3'], tol=0.019 if platform.machine() == 'arm64' else 0) +@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(), @@ -134,9 +135,8 @@ def test_collection(): 'edgecolor': 'blue'}) -@image_comparison(['tickedstroke'], remove_text=True, extensions=['png'], - tol=0.22) # Increased tolerance due to fixed clipping. -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=[ @@ -148,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() @@ -214,3 +214,19 @@ def close_group(self, s): assert renderer.open_group('s') == "open_group overridden" assert renderer.close_group('s') == "close_group overridden" + + +@check_figures_equal() +def test_simple_line_shadow(fig_test, fig_ref): + ax_ref = fig_ref.add_subplot() + ax_test = fig_test.add_subplot() + + x = np.linspace(-5, 5, 500) + y = np.exp(-x**2) + + line, = ax_test.plot( + x, y, linewidth=5, + path_effects=[ + path_effects.SimpleLineShadow(offset=(0, 0), shadow_color='blue')]) + + ax_ref.plot(x, y, linewidth=5, color='blue', alpha=0.3) diff --git a/lib/matplotlib/tests/test_pickle.py b/lib/matplotlib/tests/test_pickle.py index eb47b2668101..1590990cdeb0 100644 --- a/lib/matplotlib/tests/test_pickle.py +++ b/lib/matplotlib/tests/test_pickle.py @@ -104,7 +104,7 @@ def _generate_complete_test_figure(fig_ref): @mpl.style.context("default") -@check_figures_equal(extensions=["png"]) +@check_figures_equal() def test_complete(fig_test, fig_ref): _generate_complete_test_figure(fig_ref) # plotting is done, now test its pickle-ability @@ -136,7 +136,7 @@ def _pickle_load_subprocess(): @mpl.style.context("default") -@check_figures_equal(extensions=['png']) +@check_figures_equal() def test_pickle_load_from_subprocess(fig_test, fig_ref, tmp_path): _generate_complete_test_figure(fig_ref) @@ -150,15 +150,7 @@ def test_pickle_load_from_subprocess(fig_test, fig_ref, tmp_path): 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__, - }, + extra_env={"PICKLE_FILE_PATH": str(fp), "MPLBACKEND": "Agg"}, ) loaded_fig = pickle.loads(ast.literal_eval(proc.stdout)) diff --git a/lib/matplotlib/tests/test_png.py b/lib/matplotlib/tests/test_png.py index 9208c31df2bf..e24fe39e9ed1 100644 --- a/lib/matplotlib/tests/test_png.py +++ b/lib/matplotlib/tests/test_png.py @@ -7,7 +7,7 @@ from matplotlib import cm, pyplot as plt -@image_comparison(['pngsuite.png'], tol=0.04) +@image_comparison(['pngsuite.png'], style='default') def test_pngsuite(): files = sorted( (Path(__file__).parent / "baseline_images/pngsuite").glob("basn*.png")) @@ -20,10 +20,7 @@ def test_pngsuite(): if data.ndim == 2: # keep grayscale images gray cmap = cm.gray - # 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.imshow(data, extent=(i, i + 1, 0, 1), cmap=cmap, interpolation='nearest') plt.gca().patch.set_facecolor("#ddffff") plt.gca().set_xlim(0, len(files)) diff --git a/lib/matplotlib/tests/test_polar.py b/lib/matplotlib/tests/test_polar.py index ee38c88a123f..f688f384479b 100644 --- a/lib/matplotlib/tests/test_polar.py +++ b/lib/matplotlib/tests/test_polar.py @@ -3,11 +3,13 @@ import pytest import matplotlib as mpl +from matplotlib.projections.polar import RadialLocator from matplotlib import pyplot as plt from matplotlib.testing.decorators import image_comparison, check_figures_equal +import matplotlib.ticker as mticker -@image_comparison(['polar_axes'], style='default', tol=0.012) +@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 @@ -41,7 +43,7 @@ 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 @@ -115,7 +117,7 @@ def test_polar_units_1(fig_test, fig_ref): xs = [30.0, 45.0, 60.0, 90.0] ys = [1.0, 2.0, 3.0, 4.0] - plt.figure(fig_test.number) + plt.figure(fig_test) plt.polar([x * units.deg for x in xs], ys) ax = fig_ref.add_subplot(projection="polar") @@ -132,7 +134,7 @@ def test_polar_units_2(fig_test, fig_ref): ys = [1.0, 2.0, 3.0, 4.0] ys_km = [y * units.km for y in ys] - plt.figure(fig_test.number) + plt.figure(fig_test) # test {theta,r}units. plt.polar(xs_deg, ys_km, thetaunits="rad", runits="km") assert isinstance(plt.gca().xaxis.get_major_formatter(), @@ -144,37 +146,37 @@ 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 fig = plt.figure() - ax = fig.add_axes([0.1, 0.1, 0.8, 0.8], polar=True) + ax = fig.add_axes((0.1, 0.1, 0.8, 0.8), polar=True) ax.plot(theta, r) ax.set_rmax(2.0) 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 fig = plt.figure() - ax = fig.add_axes([0.1, 0.1, 0.8, 0.8], polar=True) + ax = fig.add_axes((0.1, 0.1, 0.8, 0.8), polar=True) ax.plot(theta, r) ax.set_rmax(0.0) 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 fig = plt.figure() - ax = fig.add_axes([0.1, 0.1, 0.8, 0.8], polar=True) + ax = fig.add_axes((0.1, 0.1, 0.8, 0.8), polar=True) ax.plot(theta, r) ax.set_rmax(2.0) ax.set_rmin(0.5) @@ -184,14 +186,14 @@ def test_polar_rorigin(): @image_comparison(['polar_invertedylim.png'], style='default') def test_polar_invertedylim(): fig = plt.figure() - ax = fig.add_axes([0.1, 0.1, 0.8, 0.8], polar=True) + ax = fig.add_axes((0.1, 0.1, 0.8, 0.8), polar=True) ax.set_ylim(2, 0) @image_comparison(['polar_invertedylim_rorigin.png'], style='default') def test_polar_invertedylim_rorigin(): fig = plt.figure() - ax = fig.add_axes([0.1, 0.1, 0.8, 0.8], polar=True) + ax = fig.add_axes((0.1, 0.1, 0.8, 0.8), polar=True) ax.yaxis.set_inverted(True) # Set the rlims to inverted (2, 0) without calling set_rlim, to check that # viewlims are correctly unstaled before draw()ing. @@ -200,19 +202,20 @@ 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 fig = plt.figure() - ax = fig.add_axes([0.1, 0.1, 0.8, 0.8], polar=True) + ax = fig.add_axes((0.1, 0.1, 0.8, 0.8), polar=True) ax.plot(theta, r) ax.set_theta_zero_location("NW", 30) ax.set_theta_direction('clockwise') -@image_comparison(['polar_rlabel_position'], style='default') +# TODO: tighten tolerance after baseline image is regenerated for text overhaul +@image_comparison(['polar_rlabel_position.png'], style='default', tol=0.07) def test_polar_rlabel_position(): fig = plt.figure() ax = fig.add_subplot(projection='polar') @@ -220,7 +223,15 @@ def test_polar_rlabel_position(): 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') + + +# TODO: tighten tolerance after baseline image is regenerated for text overhaul +@image_comparison(['polar_theta_wedge.png'], style='default', tol=0.2) def test_polar_theta_limits(): r = np.arange(0, 3.0, 0.01) theta = 2*np.pi*r @@ -253,7 +264,7 @@ def test_polar_theta_limits(): 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 +275,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]) @@ -324,7 +335,7 @@ 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. @@ -338,7 +349,7 @@ def test_polar_interpolation_steps_constant_r(fig_test, fig_ref): .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 @@ -386,7 +397,7 @@ def test_axvspan(): assert span.get_path()._interpolation_steps > 1 -@check_figures_equal(extensions=["png"]) +@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 @@ -475,6 +486,26 @@ def test_polar_log(): ax.plot(np.linspace(0, 2 * np.pi, n), np.logspace(0, 2, n)) +@check_figures_equal() +def test_polar_log_rorigin(fig_ref, fig_test): + # Test that equivalent linear and log radial settings give the same axes patch + # and spines. + ax_ref = fig_ref.add_subplot(projection='polar', facecolor='red') + ax_ref.set_rlim(0, 2) + ax_ref.set_rorigin(-3) + ax_ref.set_rticks(np.linspace(0, 2, 5)) + + ax_test = fig_test.add_subplot(projection='polar', facecolor='red') + ax_test.set_rscale('log') + ax_test.set_rlim(1, 100) + ax_test.set_rorigin(10**-3) + ax_test.set_rticks(np.logspace(0, 2, 5)) + + for ax in ax_ref, ax_test: + # Radial tick labels should be the only difference, so turn them off. + ax.tick_params(labelleft=False) + + def test_polar_neg_theta_lims(): fig = plt.figure() ax = fig.add_subplot(projection='polar') @@ -484,8 +515,8 @@ def test_polar_neg_theta_lims(): @pytest.mark.parametrize("order", ["before", "after"]) -@image_comparison(baseline_images=['polar_errorbar'], remove_text=True, - extensions=['png'], style='mpl20') +@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 @@ -499,3 +530,63 @@ def test_polar_errorbar(order): 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) + + +def test_radial_locator_wrapping(): + # Check that the locator is always wrapped inside a RadialLocator + # and that RaidialAxis.isDefault_majloc is set correctly. + fig, ax = plt.subplots(subplot_kw={'projection': 'polar'}) + assert ax.yaxis.isDefault_majloc + assert isinstance(ax.yaxis.get_major_locator(), RadialLocator) + + # set an explicit locator + locator = mticker.MaxNLocator(3) + ax.yaxis.set_major_locator(locator) + assert not ax.yaxis.isDefault_majloc + assert isinstance(ax.yaxis.get_major_locator(), RadialLocator) + assert ax.yaxis.get_major_locator().base is locator + + ax.clear() # reset to the default locator + assert ax.yaxis.isDefault_majloc + assert isinstance(ax.yaxis.get_major_locator(), RadialLocator) + + ax.set_rticks([0, 1, 2, 3]) # implicitly sets a FixedLocator + assert not ax.yaxis.isDefault_majloc # because of the fixed ticks + assert isinstance(ax.yaxis.get_major_locator(), RadialLocator) + assert isinstance(ax.yaxis.get_major_locator().base, mticker.FixedLocator) + + ax.clear() + + ax.set_rgrids([0, 1, 2, 3]) # implicitly sets a FixedLocator + assert not ax.yaxis.isDefault_majloc # because of the fixed ticks + assert isinstance(ax.yaxis.get_major_locator(), RadialLocator) + assert isinstance(ax.yaxis.get_major_locator().base, mticker.FixedLocator) + + ax.clear() + + ax.set_yscale("log") # implicitly sets a LogLocator + # Note that the LogLocator is still considered the default locator + # for the log scale + assert ax.yaxis.isDefault_majloc + assert isinstance(ax.yaxis.get_major_locator(), RadialLocator) + assert isinstance(ax.yaxis.get_major_locator().base, mticker.LogLocator) diff --git a/lib/matplotlib/tests/test_preprocess_data.py b/lib/matplotlib/tests/test_preprocess_data.py index 0684f0dbb9ae..c983d78786e1 100644 --- a/lib/matplotlib/tests/test_preprocess_data.py +++ b/lib/matplotlib/tests/test_preprocess_data.py @@ -267,7 +267,7 @@ 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] @@ -278,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 21036e177045..39ffc54bce79 100644 --- a/lib/matplotlib/tests/test_pyplot.py +++ b/lib/matplotlib/tests/test_pyplot.py @@ -1,4 +1,5 @@ import difflib +import inspect import numpy as np import sys @@ -12,7 +13,7 @@ def test_pyplot_up_to_date(tmp_path): - pytest.importorskip("black") + pytest.importorskip("black", minversion="24.1") gen_script = Path(mpl.__file__).parents[2] / "tools/boilerplate.py" if not gen_script.exists(): @@ -163,8 +164,9 @@ def test_close(): try: plt.close(1.1) except TypeError as e: - assert str(e) == "close() argument must be a Figure, an int, " \ - "a string, or None, not " + assert str(e) == ( + "'fig' must be an instance of matplotlib.figure.Figure, int, str " + "or None, not a float") def test_subplot_reuse(): @@ -380,7 +382,7 @@ def extract_documented_functions(lines): :nosignatures: plot - plot_date + errorbar """ functions = [] @@ -459,19 +461,113 @@ def test_figure_hook(): def test_multiple_same_figure_calls(): - fig = mpl.pyplot.figure(1, figsize=(1, 2)) + fig = plt.figure(1, figsize=(1, 2)) with pytest.warns(UserWarning, match="Ignoring specified arguments in this call"): - fig2 = mpl.pyplot.figure(1, figsize=(3, 4)) + fig2 = plt.figure(1, figsize=np.array([3, 4])) with pytest.warns(UserWarning, match="Ignoring specified arguments in this call"): - mpl.pyplot.figure(fig, figsize=(5, 6)) + plt.figure(fig, figsize=np.array([5, 6])) assert fig is fig2 - fig3 = mpl.pyplot.figure(1) # Checks for false warnings + fig3 = plt.figure(1) # Checks for false warnings assert fig is fig3 +def test_register_existing_figure_with_pyplot(): + from matplotlib.figure import Figure + # start with a standalone figure + fig = Figure() + assert fig.canvas.manager is None + with pytest.raises(AttributeError): + # Heads-up: This will change to returning None in the future + # See docstring for the Figure.number property + fig.number + # register the Figure with pyplot + plt.figure(fig) + assert fig.number == 1 + # the figure can now be used in pyplot + plt.suptitle("my title") + assert fig.get_suptitle() == "my title" + # it also has a manager that is properly wired up in the pyplot state + assert plt._pylab_helpers.Gcf.get_fig_manager(fig.number) is fig.canvas.manager + # and we can regularly switch the pyplot state + fig2 = plt.figure() + assert fig2.number == 2 + assert plt.figure(1) is fig + assert plt.gcf() is fig + + 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) + + +def assert_same_signature(func1, func2): + """ + Assert that `func1` and `func2` have the same arguments, + i.e. same parameter count, names and kinds. + + :param func1: First function to check + :param func2: Second function to check + """ + params1 = inspect.signature(func1).parameters + params2 = inspect.signature(func2).parameters + + assert len(params1) == len(params2) + assert all([ + params1[p].name == params2[p].name and + params1[p].kind == params2[p].kind + for p in params1 + ]) + + +def test_setloglevel_signature(): + assert_same_signature(plt.set_loglevel, mpl.set_loglevel) + + +def test_subplots_reuse_existing_figure_error(): + """Test interaction of plt.subplots(num=...) with existing figures.""" + # Create a figure with a specific number first. + fig = plt.figure(1) + + # Case 1: Reusing without clear=True should raise ValueError + with pytest.raises(ValueError, match="already exists"): + plt.subplots(num=1) + + # Case 2: Reusing WITH clear=True should work fine (no error) + fig_new, axs = plt.subplots(num=1, clear=True) + assert fig_new is fig + + # Case 3: Test passing the actual Figure object (The "Narrow Check") + with pytest.raises(ValueError, match="cannot be a FigureBase instance"): + plt.subplots(num=fig) + + plt.close(1) + + +def test_subplot_mosaic_reuse_existing_figure_error(): + """Test that plt.subplot_mosaic raises ValueError when reusing a figure.""" + fig = plt.figure(2) + + # 1. Test passing the existing figure number + with pytest.raises(ValueError, match="already exists"): + plt.subplot_mosaic([['A']], num=2) + + # 2. Test passing the actual Figure object + with pytest.raises(ValueError, match="cannot be a FigureBase instance"): + plt.subplot_mosaic([['A']], num=fig) + + # 3. Test that clear=True allows reuse without error + fig_new, axd = plt.subplot_mosaic([['A']], num=2, clear=True) + assert fig_new is fig + + plt.close(2) diff --git a/lib/matplotlib/tests/test_quiver.py b/lib/matplotlib/tests/test_quiver.py index e28b04025b5e..ef4d7a0598eb 100644 --- a/lib/matplotlib/tests/test_quiver.py +++ b/lib/matplotlib/tests/test_quiver.py @@ -26,11 +26,12 @@ def test_quiver_memory_leak(): 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', @@ -43,9 +44,9 @@ 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(): @@ -265,7 +266,7 @@ def test_quiverkey_angles(): qk = ax.quiverkey(q, 1, 1, 2, 'Label') # The arrows are only created when the key is drawn fig.canvas.draw() - assert len(qk.verts) == 1 + assert len(qk.vector.get_paths()) == 1 def test_quiverkey_angles_xy_aitoff(): @@ -294,7 +295,7 @@ def test_quiverkey_angles_xy_aitoff(): qk = ax.quiverkey(q, 0, 0, 1, '1 units') fig.canvas.draw() - assert len(qk.verts) == 1 + assert len(qk.vector.get_paths()) == 1 def test_quiverkey_angles_scale_units_cartesian(): @@ -321,7 +322,7 @@ def test_quiverkey_angles_scale_units_cartesian(): qk = ax.quiverkey(q, 0, 0, 1, '1 units') fig.canvas.draw() - assert len(qk.verts) == 1 + assert len(qk.vector.get_paths()) == 1 def test_quiver_setuvc_numbers(): @@ -380,7 +381,7 @@ def draw_quiverkey_setzorder(fig, zorder=None): @pytest.mark.parametrize('zorder', [0, 2, 5, None]) -@check_figures_equal(extensions=['png']) +@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 0aa3ec0ba603..525a9ff60d1a 100644 --- a/lib/matplotlib/tests/test_rcparams.py +++ b/lib/matplotlib/tests/test_rcparams.py @@ -13,6 +13,7 @@ import matplotlib.pyplot as plt import matplotlib.colors as mcolors import numpy as np +from matplotlib import rcsetup from matplotlib.rcsetup import ( validate_bool, validate_color, @@ -257,6 +258,8 @@ def generate_validator_testcases(valid): {'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"]) + @@ -455,6 +458,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 @@ -521,10 +530,11 @@ def test_rcparams_reset_after_fail(): @pytest.mark.skipif(sys.platform != "linux", reason="Linux only") -def test_backend_fallback_headless(tmp_path): +def test_backend_fallback_headless_invalid_backend(tmp_path): env = {**os.environ, "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_for_testing( [sys.executable, "-c", @@ -536,6 +546,28 @@ def test_backend_fallback_headless(tmp_path): 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") @@ -554,6 +586,7 @@ def test_backend_fallback_headful(tmp_path): # 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 @@ -563,40 +596,6 @@ def test_backend_fallback_headful(tmp_path): def test_deprecation(monkeypatch): - monkeypatch.setitem( - mpl._deprecated_map, "patch.linewidth", - ("0.0", "axes.linewidth", lambda old: 2 * old, lambda new: new / 2)) - with pytest.warns(mpl.MatplotlibDeprecationWarning): - assert mpl.rcParams["patch.linewidth"] \ - == mpl.rcParams["axes.linewidth"] / 2 - with pytest.warns(mpl.MatplotlibDeprecationWarning): - mpl.rcParams["patch.linewidth"] = 1 - assert mpl.rcParams["axes.linewidth"] == 2 - - monkeypatch.setitem( - mpl._deprecated_ignore_map, "patch.edgecolor", - ("0.0", "axes.edgecolor")) - with pytest.warns(mpl.MatplotlibDeprecationWarning): - assert mpl.rcParams["patch.edgecolor"] \ - == mpl.rcParams["axes.edgecolor"] - with pytest.warns(mpl.MatplotlibDeprecationWarning): - mpl.rcParams["patch.edgecolor"] = "#abcd" - assert mpl.rcParams["axes.edgecolor"] != "#abcd" - - monkeypatch.setitem( - mpl._deprecated_ignore_map, "patch.force_edgecolor", - ("0.0", None)) - with pytest.warns(mpl.MatplotlibDeprecationWarning): - assert mpl.rcParams["patch.force_edgecolor"] is None - - monkeypatch.setitem( - mpl._deprecated_remain_as_none, "svg.hashsalt", - ("0.0",)) - with pytest.warns(mpl.MatplotlibDeprecationWarning): - mpl.rcParams["svg.hashsalt"] = "foobar" - assert mpl.rcParams["svg.hashsalt"] == "foobar" # Doesn't warn. - mpl.rcParams["svg.hashsalt"] = None # Doesn't warn. - 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 @@ -656,3 +655,39 @@ def test_rcparams_path_sketch_from_file(tmp_path, value): 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 + + +def test_all_params_defined_as_code(): + assert set(p.name for p in rcsetup._params_list()) == set(mpl.rcParams.keys()) + + +def test_validators_defined_as_code(): + for param in rcsetup._params_list(): + validator = rcsetup._convert_validator_spec(param.name, param.validator) + assert validator == rcsetup._validators[param.name] + + +def test_defaults_as_code(): + for param in rcsetup._params_list(): + if param.name == 'backend': + # backend has special handling and no meaningful default + continue + assert param.default == mpl.rcParamsDefault[param.name], param.name diff --git a/lib/matplotlib/tests/test_sankey.py b/lib/matplotlib/tests/test_sankey.py index cbb7f516a65c..745db5f767b2 100644 --- a/lib/matplotlib/tests/test_sankey.py +++ b/lib/matplotlib/tests/test_sankey.py @@ -6,7 +6,7 @@ def test_sankey(): - # lets just create a sankey instance and check the code runs + # let's just create a sankey instance and check the code runs sankey = Sankey() sankey.add() @@ -91,7 +91,7 @@ def test_sankey2(): (0.75, -0.8599479)]) -@check_figures_equal(extensions=['png']) +@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], diff --git a/lib/matplotlib/tests/test_scale.py b/lib/matplotlib/tests/test_scale.py index 727397367762..9f882103967e 100644 --- a/lib/matplotlib/tests/test_scale.py +++ b/lib/matplotlib/tests/test_scale.py @@ -6,8 +6,12 @@ LogTransform, InvertedLogTransform, SymmetricalLogTransform) import matplotlib.scale as mscale -from matplotlib.ticker import AsinhLocator, LogFormatterSciNotation +from matplotlib.ticker import ( + AsinhLocator, AutoLocator, LogFormatterSciNotation, + NullFormatter, NullLocator, ScalarFormatter +) from matplotlib.testing.decorators import check_figures_equal, image_comparison +from matplotlib.transforms import IdentityTransform import numpy as np from numpy.testing import assert_allclose @@ -107,7 +111,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(): @@ -162,6 +167,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(): @@ -293,3 +299,137 @@ def test_bad_scale(self): AsinhScale(axis=None, linear_width=-1) s0 = AsinhScale(axis=None, ) s1 = AsinhScale(axis=None, linear_width=3.0) + + +def test_custom_scale_without_axis(): + """ + Test that one can register and use custom scales that don't take an *axis* param. + """ + class CustomTransform(IdentityTransform): + pass + + class CustomScale(mscale.ScaleBase): + name = "custom" + + # Important: __init__ has no *axis* parameter + def __init__(self): + self._transform = CustomTransform() + + def get_transform(self): + return self._transform + + def set_default_locators_and_formatters(self, axis): + axis.set_major_locator(AutoLocator()) + axis.set_major_formatter(ScalarFormatter()) + axis.set_minor_locator(NullLocator()) + axis.set_minor_formatter(NullFormatter()) + + try: + mscale.register_scale(CustomScale) + fig, ax = plt.subplots() + ax.set_xscale('custom') + assert isinstance(ax.xaxis.get_transform(), CustomTransform) + finally: + # cleanup - there's no public unregister_scale() + del mscale._scale_mapping["custom"] + del mscale._scale_has_axis_parameter["custom"] + + +def test_custom_scale_with_axis(): + """ + Test that one can still register and use custom scales with an *axis* + parameter, but that registering issues a pending-deprecation warning. + """ + class CustomTransform(IdentityTransform): + pass + + class CustomScale(mscale.ScaleBase): + name = "custom" + + # Important: __init__ still has the *axis* parameter + def __init__(self, axis): + self._transform = CustomTransform() + + def get_transform(self): + return self._transform + + def set_default_locators_and_formatters(self, axis): + axis.set_major_locator(AutoLocator()) + axis.set_major_formatter(ScalarFormatter()) + axis.set_minor_locator(NullLocator()) + axis.set_minor_formatter(NullFormatter()) + + try: + with pytest.warns( + PendingDeprecationWarning, + match=r"'axis' parameter .* is pending-deprecated"): + mscale.register_scale(CustomScale) + fig, ax = plt.subplots() + ax.set_xscale('custom') + assert isinstance(ax.xaxis.get_transform(), CustomTransform) + finally: + # cleanup - there's no public unregister_scale() + del mscale._scale_mapping["custom"] + del mscale._scale_has_axis_parameter["custom"] + + +def test_val_in_range(): + + test_cases = [ + # LinearScale: Always True (even for Inf/NaN) + ('linear', 10.0, True), + ('linear', -10.0, True), + ('linear', 0.0, True), + ('linear', np.inf, False), + ('linear', np.nan, False), + + # LogScale: Only positive values (> 0) + ('log', 1.0, True), + ('log', 1e-300, True), + ('log', 0.0, False), + ('log', -1.0, False), + ('log', np.inf, False), + ('log', np.nan, False), + + # LogitScale: Strictly between 0 and 1 + ('logit', 0.5, True), + ('logit', 0.0, False), + ('logit', 1.0, False), + ('logit', -0.1, False), + ('logit', 1.1, False), + ('logit', np.inf, False), + ('logit', np.nan, False), + + # SymmetricalLogScale: Valid for all real numbers + # Uses ScaleBase fallback. NaN returns False since NaN != NaN + ('symlog', 10.0, True), + ('symlog', -10.0, True), + ('symlog', 0.0, True), + ('symlog', np.inf, False), + ('symlog', np.nan, False), + ] + + for name, val, expected in test_cases: + scale_cls = mscale._scale_mapping[name] + s = scale_cls(axis=None) + + result = s.val_in_range(val) + assert result is expected, ( + f"Failed {name}.val_in_range({val})." + f"Expected {expected}, got {result}" + ) + + +def test_val_in_range_base_fallback(): + # Directly test the ScaleBase fallback for custom scales. + # ScaleBase.limit_range_for_scale returns values unchanged by default + s = mscale.ScaleBase(axis=None) + + # Normal values should be True + assert s.val_in_range(1.0) is True + assert s.val_in_range(-5.5) is True + + # NaN and Inf returns False since they cannot be drawn in a plot + assert s.val_in_range(np.nan) is False + assert s.val_in_range(np.inf) is False + assert s.val_in_range(-np.inf) is False diff --git a/lib/matplotlib/tests/test_simplification.py b/lib/matplotlib/tests/test_simplification.py index a052c24cb655..98d3728b1d34 100644 --- a/lib/matplotlib/tests/test_simplification.py +++ b/lib/matplotlib/tests/test_simplification.py @@ -25,11 +25,11 @@ def test_clipping(): fig, ax = plt.subplots() ax.plot(t, s, linewidth=1.0) - ax.set_ylim((-0.20, -0.28)) + ax.set_ylim(-0.20, -0.28) @image_comparison(['overflow'], remove_text=True, - tol=0.007 if platform.machine() == 'arm64' else 0) + 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)) @@ -244,11 +244,11 @@ def test_simplify_curve(): fig, ax = plt.subplots() ax.add_patch(pp1) - ax.set_xlim((0, 2)) - ax.set_ylim((0, 2)) + ax.set_xlim(0, 2) + ax.set_ylim(0, 2) -@check_figures_equal() +@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() @@ -356,7 +356,7 @@ def test_closed_path_nan_removal(fig_test, fig_ref): remove_ticks_and_titles(fig_ref) -@check_figures_equal() +@check_figures_equal(extensions=['png', 'pdf', 'svg']) def test_closed_path_clipping(fig_test, fig_ref): vertices = [] for roll in range(8): @@ -401,8 +401,8 @@ def test_closed_path_clipping(fig_test, fig_ref): def test_hatch(): fig, ax = plt.subplots() ax.add_patch(plt.Rectangle((0, 0), 1, 1, fill=False, hatch="/")) - ax.set_xlim((0.45, 0.55)) - ax.set_ylim((0.45, 0.55)) + ax.set_xlim(0.45, 0.55) + ax.set_ylim(0.45, 0.55) @image_comparison(['fft_peaks'], remove_text=True) @@ -455,7 +455,7 @@ def test_start_with_moveto(): assert segs[0][1] == Path.MOVETO -def test_throw_rendering_complexity_exceeded(): +def test_throw_rendering_complexity_exceeded(high_memory): plt.rcParams['path.simplify'] = False xx = np.arange(2_000_000) yy = np.random.rand(2_000_000) diff --git a/lib/matplotlib/tests/test_skew.py b/lib/matplotlib/tests/test_skew.py index fd7e7cebfacb..125ecd7ff606 100644 --- a/lib/matplotlib/tests/test_skew.py +++ b/lib/matplotlib/tests/test_skew.py @@ -25,9 +25,9 @@ def draw(self, renderer): for artist in [self.gridline, self.tick1line, self.tick2line, self.label1, self.label2]: stack.callback(artist.set_visible, artist.get_visible()) - needs_lower = transforms.interval_contains( + needs_lower = transforms._interval_contains( self.axes.lower_xlim, self.get_loc()) - needs_upper = transforms.interval_contains( + needs_upper = transforms._interval_contains( self.axes.upper_xlim, self.get_loc()) self.tick1line.set_visible( self.tick1line.get_visible() and needs_lower) @@ -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,8 +145,8 @@ def test_set_line_coll_dash_image(): ax.axvline(0, color='b') -@image_comparison(['skew_rects'], remove_text=True, - tol=0.009 if platform.machine() == 'arm64' else 0) +@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)) diff --git a/lib/matplotlib/tests/test_sphinxext.py b/lib/matplotlib/tests/test_sphinxext.py index 6e7b5ec5e50e..c6f4e13c74c2 100644 --- a/lib/matplotlib/tests/test_sphinxext.py +++ b/lib/matplotlib/tests/test_sphinxext.py @@ -13,14 +13,21 @@ pytest.importorskip('sphinx', minversion='4.1.3') +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(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": ""}) + env={**os.environ, "MPLBACKEND": "", "GCOV_ERROR_FILE": os.devnull} + ) out = proc.stdout err = proc.stderr @@ -33,24 +40,12 @@ def build_sphinx_html(source_dir, doctree_dir, html_dir, extra_args=None): def test_tinypages(tmp_path): - shutil.copytree(Path(__file__).parent / 'tinypages', tmp_path, - dirs_exist_ok=True) + shutil.copytree(tinypages, tmp_path, dirs_exist_ok=True, + ignore=shutil.ignore_patterns('_build', 'doctrees', + 'plot_directive')) html_dir = tmp_path / '_build' / 'html' img_dir = html_dir / '_images' doctree_dir = tmp_path / 'doctrees' - # Build the pages with warnings turned into errors - cmd = [sys.executable, '-msphinx', '-W', '-b', 'html', - '-d', str(doctree_dir), - str(Path(__file__).parent / 'tinypages'), str(html_dir)] - # 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 # Build the pages with warnings turned into errors build_sphinx_html(tmp_path, doctree_dir, html_dir) @@ -75,27 +70,35 @@ def plot_directive_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() + html_contents = (html_dir / 'some_plots.html').read_text(encoding='utf-8') - assert b'# Only a comment' in html_contents + assert '# Only a comment' in html_contents # check plot defined in external file. 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 b'This is the caption for plot 15.' in html_contents - # check if figure caption using :caption: made it into html file - assert b'Plot 17 uses the caption option.' in html_contents + 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 18.' 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 b'plot-directive my-class my-other-class' in html_contents + assert 'plot-directive my-class my-other-class' in html_contents # check that the multi-image caption is applied twice - assert html_contents.count(b'This caption applies to both plots.') == 2 + 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') + # plots 23--25 use a custom basename + assert filecmp.cmp(range_6, img_dir / 'custom-basename-6.png') + assert filecmp.cmp(range_4, img_dir / 'custom-basename-4.png') + assert filecmp.cmp(range_4, img_dir / 'custom-basename-4-6_00.png') + assert filecmp.cmp(range_6, img_dir / 'custom-basename-4-6_01.png') # Modify the included plot contents = (tmp_path / 'included_plot_21.rst').read_bytes() @@ -122,9 +125,8 @@ def plot_directive_file(num): def test_plot_html_show_source_link(tmp_path): - parent = Path(__file__).parent - shutil.copyfile(parent / 'tinypages/conf.py', tmp_path / 'conf.py') - shutil.copytree(parent / 'tinypages/_static', tmp_path / '_static') + 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:: @@ -147,9 +149,8 @@ def test_plot_html_show_source_link(tmp_path): 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. - parent = Path(__file__).parent - shutil.copyfile(parent / 'tinypages/conf.py', tmp_path / 'conf.py') - shutil.copytree(parent / 'tinypages/_static', tmp_path / '_static') + 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:: @@ -167,9 +168,8 @@ def test_show_source_link_true(tmp_path, plot_html_show_source_link): 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. - parent = Path(__file__).parent - shutil.copyfile(parent / 'tinypages/conf.py', tmp_path / 'conf.py') - shutil.copytree(parent / 'tinypages/_static', tmp_path / '_static') + 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:: @@ -183,15 +183,62 @@ def test_show_source_link_false(tmp_path, plot_html_show_source_link): assert len(list(html_dir.glob("**/index-1.py"))) == 0 +def test_plot_html_show_source_link_custom_basename(tmp_path): + # Test that source link filename includes .py extension when using custom basename + 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:: + :filename-prefix: custom-name + + plt.plot(range(2)) +""") + html_dir = tmp_path / '_build' / 'html' + build_sphinx_html(tmp_path, doctree_dir, html_dir) + + # Check that source file with .py extension is generated + assert len(list(html_dir.glob("**/custom-name.py"))) == 1 + + # Check that the HTML contains the correct link with .py extension + html_content = (html_dir / 'index.html').read_text() + assert 'custom-name.py' in html_content + + +def test_plot_html_code_caption(tmp_path): + # Test that :code-caption: option adds caption to code block + 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:: + :include-source: + :code-caption: Example plotting code + + import matplotlib.pyplot as plt + plt.plot([1, 2, 3], [1, 4, 9]) +""") + html_dir = tmp_path / '_build' / 'html' + build_sphinx_html(tmp_path, doctree_dir, html_dir) + + # Check that the HTML contains the code caption + html_content = (html_dir / 'index.html').read_text(encoding='utf-8') + assert 'Example plotting code' in html_content + # Verify the caption is associated with the code block + # (appears in a caption element) + assert '

info.misses +def test_metrics_cache2(): + # dig into the signature to get the mutable default used as a cache + renderer_cache = inspect.signature( + mpl.text._get_text_metrics_function + ).parameters['_cache'].default + gc.collect() + renderer_cache.clear() + + def helper(): + fig, ax = plt.subplots() + fig.draw_without_rendering() + # show we hit the outer cache + assert len(renderer_cache) == 1 + func = renderer_cache[fig.canvas.get_renderer()] + cache_info = func.cache_info() + # show we hit the inner cache + assert cache_info.currsize > 0 + assert cache_info.currsize == cache_info.misses + assert cache_info.hits > cache_info.misses + plt.close(fig) + + helper() + gc.collect() + # show the outer cache has a lifetime tied to the renderer (via the figure) + assert len(renderer_cache) == 0 + + def test_annotate_offset_fontsize(): # Test that offset_fontsize parameter works and uses accurate values fig, ax = plt.subplots() @@ -958,7 +990,7 @@ def test_annotation_antialiased(): assert annot4._antialiased == mpl.rcParams['text.antialiased'] -@check_figures_equal(extensions=["png"]) +@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() @@ -974,7 +1006,7 @@ def test_annotate_and_offsetfrom_copy_input(fig_test, fig_ref): an_xy[:] = 2 -@check_figures_equal() +@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) @@ -983,7 +1015,7 @@ def test_text_antialiased_off_default_vs_manual(fig_test, fig_ref): fig_ref.text(0.5, 0.5, '6 inches x 2 inches') -@check_figures_equal() +@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) @@ -1103,8 +1135,8 @@ def test_empty_annotation_get_window_extent(): assert points[0, 1] == 50.0 -@image_comparison(baseline_images=['basictext_wrap'], - extensions=['png']) +# TODO: tighten tolerance after baseline image is regenerated for text overhaul +@image_comparison(['basictext_wrap.png'], tol=0.3) def test_basic_wrap(): fig = plt.figure() plt.axis([0, 10, 0, 10]) @@ -1120,8 +1152,8 @@ def test_basic_wrap(): plt.text(-1, 0, t, ha='left', rotation=-15, wrap=True) -@image_comparison(baseline_images=['fonttext_wrap'], - extensions=['png']) +# TODO: tighten tolerance after baseline image is regenerated for text overhaul +@image_comparison(['fonttext_wrap.png'], tol=0.3) def test_font_wrap(): fig = plt.figure() plt.axis([0, 10, 0, 10]) @@ -1135,3 +1167,72 @@ def test_font_wrap(): 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'] + + +# TODO: tighten tolerance after baseline image is regenerated for text overhaul +@image_comparison(['xtick_rotation_mode.png'], remove_text=False, style='mpl20', + tol=0.3) +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) + + +# TODO: tighten tolerance after baseline image is regenerated for text overhaul +@image_comparison(['ytick_rotation_mode.png'], remove_text=False, style='mpl20', + tol=0.3) +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) + + +def test_text_tightbbox_outside_scale_domain(): + # Test that text at positions outside the valid domain of axes scales + # (e.g., negative coordinates with log scale) returns a null bbox. + fig, ax = plt.subplots() + ax.set_yscale('log') + ax.set_ylim(1, 100) + + invalid_text = ax.text(0, -5, 'invalid') + invalid_bbox = invalid_text.get_tightbbox(fig.canvas.get_renderer()) + assert not np.isfinite(invalid_bbox.width) diff --git a/lib/matplotlib/tests/test_ticker.py b/lib/matplotlib/tests/test_ticker.py index 222a0d7e11b0..478a54b8a317 100644 --- a/lib/matplotlib/tests/test_ticker.py +++ b/lib/matplotlib/tests/test_ticker.py @@ -6,7 +6,7 @@ from packaging.version import parse as parse_version import numpy as np -from numpy.testing import assert_almost_equal, assert_array_equal +from numpy.testing import assert_almost_equal, assert_array_equal, assert_allclose import pytest import matplotlib as mpl @@ -332,13 +332,11 @@ 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): @@ -358,6 +356,10 @@ def test_switch_to_autolocator(self): loc = mticker.LogLocator(subs=np.arange(2, 10)) assert 1.0 not in loc.tick_values(0.9, 20.) assert 10.0 not in loc.tick_values(0.9, 20.) + # don't switch if there's already one major and one minor tick (10 & 20) + loc = mticker.LogLocator(subs="auto") + tv = loc.tick_values(10, 20) + assert_array_equal(tv[(10 <= tv) & (tv <= 20)], [20]) def test_set_params(self): """ @@ -377,7 +379,7 @@ def test_tick_values_correct(self): 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]) + 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): @@ -387,8 +389,7 @@ def test_tick_values_not_empty(self): 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, - 1.e+09, 2.e+09, 5.e+09]) + 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): @@ -603,6 +604,22 @@ def test_set_params(self): assert index._base == 7 assert index.offset == 7 + def test_tick_values_not_exceeding_vmax(self): + """ + Test that tick_values does not return values greater than vmax. + """ + # Test case where offset=0 could cause vmax to be included incorrectly + index = mticker.IndexLocator(base=1, offset=0) + assert_array_equal(index.tick_values(0, 4), [0, 1, 2, 3, 4]) + + # Test case with fractional offset + index = mticker.IndexLocator(base=1, offset=0.5) + assert_array_equal(index.tick_values(0, 4), [0.5, 1.5, 2.5, 3.5]) + + # Test case with base > 1 + index = mticker.IndexLocator(base=2, offset=0) + assert_array_equal(index.tick_values(0, 5), [0, 2, 4]) + class TestSymmetricalLogLocator: def test_set_params(self): @@ -859,6 +876,22 @@ def test_set_use_offset_float(self): 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'] @@ -1235,11 +1268,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)) @@ -1591,6 +1629,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 @@ -1825,14 +1930,57 @@ def test_bad_locator_subs(sub): ll.set_params(subs=sub) -@pytest.mark.parametrize('numticks', [1, 2, 3, 9]) +@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): - ll = mticker.LogLocator() - ll.set_params(numticks=numticks) - for top in [5, 7, 9, 11, 15, 50, 100, 1000]: - ticks = ll.tick_values(.5, top) - assert (np.diff(np.log10(ll.tick_values(6, 150))) == 1).all() +def test_small_range_loglocator(numticks, lims, ticks): + ll = mticker.LogLocator(numticks=numticks) + if parse_version(np.version.version).major < 2: + assert_allclose(ll.tick_values(*lims), ticks, rtol=2e-16) + else: + 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(): diff --git a/lib/matplotlib/tests/test_tightlayout.py b/lib/matplotlib/tests/test_tightlayout.py index 9c654f4d1f48..98fd5e70cdb9 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,7 +69,7 @@ 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() @@ -74,7 +78,7 @@ def test_tight_layout5(): 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,7 +134,7 @@ def test_tight_layout7(): plt.tight_layout() -@image_comparison(['tight_layout8'], tol=0.005) +@image_comparison(['tight_layout8'], style='mpl20', tol=0.005) def test_tight_layout8(): """Test automatic use of tight_layout.""" fig = plt.figure() @@ -140,7 +144,7 @@ def test_tight_layout8(): 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 @@ -174,10 +178,10 @@ def test_outward_ticks(): # These values were obtained after visual checking that they correspond # to a tight layouting that did take the ticks into account. expected = [ - [[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]], + [[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), @@ -190,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, @@ -211,47 +215,78 @@ 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(): @@ -296,8 +331,8 @@ def test_collapsed(): # 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.set_xlim(0, 1) + ax.set_ylim(0, 1) ax.annotate('BIG LONG STRING', xy=(1.25, 2), xytext=(10.5, 1.75), annotation_clip=False) diff --git a/lib/matplotlib/tests/test_transforms.py b/lib/matplotlib/tests/test_transforms.py index 96e78b6828f8..2b4351a5cfbb 100644 --- a/lib/matplotlib/tests/test_transforms.py +++ b/lib/matplotlib/tests/test_transforms.py @@ -694,9 +694,9 @@ 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) + x, y = blend.contains_branch_separately(self.stack2_subset) stack_blend = self.tn3 + blend - sx, sy = stack_blend.contains_branch_seperately(self.stack2_subset) + sx, sy = stack_blend.contains_branch_separately(self.stack2_subset) assert x is sx is False assert y is sy is True @@ -835,6 +835,16 @@ def assert_bbox_eq(bbox1, bbox2): assert_array_equal(bbox1.bounds, bbox2.bounds) +def test_bbox_is_finite(): + assert not Bbox([(1, 1), (1, 1)])._is_finite() + assert not Bbox([(0, 0), (np.inf, 1)])._is_finite() + assert not Bbox([(-np.inf, 0), (2, 2)])._is_finite() + assert not Bbox([(np.nan, 0), (2, 2)])._is_finite() + assert Bbox([(0, 0), (0, 2)])._is_finite() + assert Bbox([(0, 0), (2, 0)])._is_finite() + assert Bbox([(0, 0), (1, 2)])._is_finite() + + 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() @@ -891,8 +901,7 @@ def test_str_transform(): Affine2D().scale(1.0))), PolarTransform( PolarAxes(0.125,0.1;0.775x0.8), - use_rmin=True, - apply_theta_transforms=False)), + use_rmin=True)), CompositeGenericTransform( CompositeGenericTransform( PolarAffine( @@ -968,7 +977,7 @@ def test_nonsingular(): zero_expansion = np.array([-0.001, 0.001]) cases = [(0, np.nan), (0, 0), (0, 7.9e-317)] for args in cases: - out = np.array(mtransforms.nonsingular(*args)) + out = np.array(mtransforms._nonsingular(*args)) assert_array_equal(out, zero_expansion) @@ -987,12 +996,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() @@ -1053,7 +1056,7 @@ def test_transformwrapper(): t.set(scale.LogTransform(10)) -@check_figures_equal(extensions=["png"]) +@check_figures_equal() def test_scale_swapping(fig_test, fig_ref): np.random.seed(19680801) samples = np.random.normal(size=10) @@ -1090,21 +1093,21 @@ def test_transformedbbox_contains(): 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) + 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) + 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(): diff --git a/lib/matplotlib/tests/test_triangulation.py b/lib/matplotlib/tests/test_triangulation.py index 337443eb1e27..ae065a231fd9 100644 --- a/lib/matplotlib/tests/test_triangulation.py +++ b/lib/matplotlib/tests/test_triangulation.py @@ -612,7 +612,7 @@ def test_triinterpcubic_cg_solver(): # 1) A commonly used test involves a 2d Poisson matrix. def poisson_sparse_matrix(n, m): """ - Return the sparse, (n*m, n*m) matrix in coo format resulting from the + Return the sparse, (n*m, n*m) matrix in COO format resulting from the discretisation of the 2-dimensional Poisson equation according to a finite difference numerical scheme on a uniform (n, m) grid. """ diff --git a/lib/matplotlib/tests/test_type1font.py b/lib/matplotlib/tests/test_type1font.py index 9b8a2d1f07c6..b2f93ef28a26 100644 --- a/lib/matplotlib/tests/test_type1font.py +++ b/lib/matplotlib/tests/test_type1font.py @@ -5,7 +5,7 @@ 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}) @@ -78,7 +78,7 @@ def test_Type1Font(): def test_Type1Font_2(): - filename = os.path.join(os.path.dirname(__file__), + filename = os.path.join(os.path.dirname(__file__), 'data', 'Courier10PitchBT-Bold.pfb') font = t1f.Type1Font(filename) assert font.prop['Weight'] == 'Bold' @@ -137,7 +137,7 @@ def test_tokenize_errors(): 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() diff --git a/lib/matplotlib/tests/test_typing.py b/lib/matplotlib/tests/test_typing.py new file mode 100644 index 000000000000..c9fc8e5b162f --- /dev/null +++ b/lib/matplotlib/tests/test_typing.py @@ -0,0 +1,51 @@ +import re +import typing +from pathlib import Path + +import matplotlib.pyplot as plt +from matplotlib.colors import Colormap +from matplotlib.typing import RcKeyType, RcGroupKeyType + + +def test_cm_stub_matches_runtime_colormaps(): + runtime_cm = plt.cm + runtime_cmaps = { + name + for name, value in vars(runtime_cm).items() + if isinstance(value, Colormap) + } + + cm_pyi_path = Path(__file__).parent.parent / "cm.pyi" + assert cm_pyi_path.exists(), f"{cm_pyi_path} does not exist" + + pyi_content = cm_pyi_path.read_text(encoding='utf-8') + + stubbed_cmaps = set( + re.findall(r"^(\w+):\s+colors\.Colormap", pyi_content, re.MULTILINE) + ) + + assert runtime_cmaps, ( + "No colormaps variables found at runtime in matplotlib.colors" + ) + assert stubbed_cmaps, ( + "No colormaps found in cm.pyi" + ) + + assert runtime_cmaps == stubbed_cmaps + + +def test_rcparam_stubs(): + runtime_rc_keys = { + name for name in plt.rcParamsDefault.keys() + if not name.startswith('_') + } + + assert {*typing.get_args(RcKeyType)} == runtime_rc_keys + + runtime_rc_group_keys = set() + for name in runtime_rc_keys: + groups = name.split('.') + for i in range(1, len(groups)): + runtime_rc_group_keys.add('.'.join(groups[:i])) + + assert {*typing.get_args(RcGroupKeyType)} == runtime_rc_group_keys diff --git a/lib/matplotlib/tests/test_units.py b/lib/matplotlib/tests/test_units.py index ae6372fea1e1..c13c54a101fc 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 @@ -78,8 +80,9 @@ def default_units(value, axis): # Tests that the conversion machinery works properly for classes that # work as a facade over numpy arrays (like pint) +# TODO: tighten tolerance after baseline image is regenerated for text overhaul @image_comparison(['plot_pint.png'], style='mpl20', - tol=0 if platform.machine() == 'x86_64' else 0.03) + tol=0.03 if platform.machine() == 'x86_64' else 0.04) def test_numpy_facade(quantity_converter): # use former defaults to match existing baseline image plt.rcParams['axes.formatter.limits'] = -7, 7 @@ -140,8 +143,9 @@ def test_jpl_bar_units(): ax.set_ylim([b - 1 * day, b + w[-1] + (1.001) * day]) +# TODO: tighten tolerance after baseline image is regenerated for text overhaul @image_comparison(['jpl_barh_units.png'], - savefig_kwarg={'dpi': 120}, style='mpl20') + savefig_kwarg={'dpi': 120}, style='mpl20', tol=0.02) def test_jpl_barh_units(): import matplotlib.testing.jpl_units as units units.register() @@ -189,7 +193,7 @@ def test_errorbar_mixed_units(): fig.canvas.draw() -@check_figures_equal(extensions=["png"]) +@check_figures_equal() def test_subclass(fig_test, fig_ref): class subdate(datetime): pass @@ -236,6 +240,39 @@ def test_shared_axis_categorical(): 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() @@ -302,3 +339,17 @@ 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 342face4504f..78d9fd6cc948 100644 --- a/lib/matplotlib/tests/test_usetex.py +++ b/lib/matplotlib/tests/test_usetex.py @@ -1,3 +1,4 @@ +import re from tempfile import TemporaryFile import numpy as np @@ -42,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, "$-$") @@ -156,20 +157,84 @@ def test_missing_psfont(fmt, monkeypatch): 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 +# TODO: tighten tolerance after baseline image is regenerated for text overhaul @image_comparison(baseline_images=['rotation'], extensions=['eps', 'pdf', 'png', 'svg'], - style='mpl20', tol=3.91 if _old_gs_version else 0) + style='mpl20', tol=3.91 if _old_gs_version else 0.2) 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) + 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' @@ -185,3 +250,10 @@ def test_rotation(): # '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 585d846944e8..0ac24dc8e8e4 100644 --- a/lib/matplotlib/tests/test_widgets.py +++ b/lib/matplotlib/tests/test_widgets.py @@ -3,13 +3,13 @@ import operator from unittest import mock -from matplotlib.backend_bases import MouseEvent +import matplotlib as mpl +from matplotlib.backend_bases import DrawEvent, KeyEvent, MouseEvent import matplotlib.colors as mcolors import matplotlib.widgets as widgets import matplotlib.pyplot as plt 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) +from matplotlib.testing.widgets import click_and_drag, get_ax, noop import numpy as np from numpy.testing import assert_allclose @@ -71,11 +71,10 @@ 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) - + MouseEvent._from_ax_coords("button_press_event", ax, (100, 100), 1)._process() + MouseEvent._from_ax_coords("motion_notify_event", ax, (199, 199), 1)._process() # purposely drag outside of axis for release - do_event(tool, 'release', xdata=250, ydata=250, button=1) + MouseEvent._from_ax_coords("button_release_event", ax, (250, 250), 1)._process() if kwargs.get('drawtype', None) not in ['line', 'none']: assert_allclose(tool.geometry, @@ -137,7 +136,7 @@ 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)) + click_and_drag(tool, start=(10, 10), end=(90, 120)) assert tool.center == (50, 65) # Drag inside rectangle, but away from centre handle # @@ -178,8 +177,8 @@ def test_rectangle_selector_set_props_handle_props(ax): 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) + click_and_drag(tool, start=(10, 10), end=(100, 120)) + assert tool.extents == (10.0, 100.0, 10.0, 120.0) # resize NE handle extents = tool.extents @@ -446,11 +445,11 @@ def test_rectangle_rotate(ax, selector_class): assert len(tool._state) == 0 # Rotate anticlockwise using top-right corner - do_event(tool, 'on_key_press', key='r') + KeyEvent("key_press_event", ax.figure.canvas, "r")._process() 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') + KeyEvent("key_press_event", ax.figure.canvas, "r")._process() assert len(tool._state) == 0 # Extents shouldn't change (as shape of rectangle hasn't changed) assert tool.extents == (100, 130, 100, 140) @@ -518,6 +517,17 @@ def test_rectangle_resize_square_center_aspect(ax, use_data_coordinates): 46.25, 133.75]) +def test_axeswidget_del_on_failed_init(): + """ + Test that an unraisable exception is not created when initialization + fails. + """ + # Pytest would fail the test if such an exception occurred. + fig, ax = plt.subplots() + with pytest.raises(TypeError, match="unexpected keyword argument 'undefined'"): + widgets.Button(ax, undefined='bar') + + def test_ellipse(ax): """For ellipse, test out the key modifiers""" tool = widgets.EllipseSelector(ax, grab_range=10, interactive=True) @@ -623,27 +633,36 @@ def test_rectangle_selector_ignore_outside(ax, ignore_event_outside): ('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 - - # 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. + # Also test that span selectors work in the presence of twin axes or for + # outside-inset 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) - # 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) + ax.twinx() + child = ax.inset_axes([0, 1, 1, 1], xlim=(0, 200), ylim=(0, 200)) + + for target in [ax, child]: + selected = [] + def onselect(*args): selected.append(args) + moved = [] + def onmove(*args): moved.append(args) + if onmove_callback: + kwargs['onmove_callback'] = onmove + + tool = widgets.SpanSelector(target, onselect, orientation, **kwargs) + MouseEvent._from_ax_coords( + "button_press_event", target, (100, 100), 1)._process() + # move outside of axis + MouseEvent._from_ax_coords( + "motion_notify_event", target, (199, 199), 1)._process() + MouseEvent._from_ax_coords( + "button_release_event", target, (250, 250), 1)._process() + + # tol is set by pixel size (~100 pixels & span of 200 data units) + assert_allclose(selected, [(100, 199)], atol=.5) + if onmove_callback: + assert_allclose(moved, [(100, 199)], atol=.5) @pytest.mark.parametrize('interactive', [True, False]) @@ -783,7 +802,7 @@ def test_selector_clear(ax, selector): click_and_drag(tool, start=(130, 130), end=(130, 130)) assert tool._selection_completed - do_event(tool, 'on_key_press', key='escape') + KeyEvent("key_press_event", ax.figure.canvas, "escape")._process() assert not tool._selection_completed @@ -905,10 +924,8 @@ def mean(vmin, vmax): # 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) + MouseEvent._from_ax_coords("button_press_event", ax, (1, 2), 1)._process() + MouseEvent._from_ax_coords("motion_notify_event", ax, (2, 2), 1)._process() assert span._get_animated_artists() == (ln, ln2) assert ln.stale is False assert ln2.stale @@ -918,16 +935,12 @@ def mean(vmin, vmax): # 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) + MouseEvent._from_ax_coords("button_press_event", ax, (4, 0), 1)._process() + MouseEvent._from_ax_coords("motion_notify_event", ax, (5, 2), 1)._process() 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) + MouseEvent._from_ax_coords("button_release_event", ax, (5, 2), 1)._process() assert ln2.stale is False @@ -988,9 +1001,9 @@ 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) + MouseEvent._from_ax_coords("button_press_event", ax, (100, 100), 1)._process() + MouseEvent._from_ax_coords("motion_notify_event", ax, (125, 125), 1)._process() + MouseEvent._from_ax_coords("button_release_event", ax, (150, 150), 1)._process() onselect.assert_called_once_with([(100, 100), (125, 125), (150, 150)]) @@ -1066,7 +1079,7 @@ def test_TextBox(ax, toolbar): assert tool.text == '' - do_event(tool, '_click') + MouseEvent._from_ax_coords("button_press_event", ax, (.5, .5), 1)._process() tool.set_val('x**2') @@ -1078,9 +1091,9 @@ def test_TextBox(ax, toolbar): assert submit_event.call_count == 2 - 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') + MouseEvent._from_ax_coords("button_press_event", ax, (.5, .5), 1)._process() + KeyEvent("key_press_event", ax.figure.canvas, "+")._process() + KeyEvent("key_press_event", ax.figure.canvas, "5")._process() assert text_change_event.call_count == 3 @@ -1126,7 +1139,7 @@ def test_check_radio_buttons_image(): check_props={'color': ['red', 'green', 'blue']}) -@check_figures_equal(extensions=["png"]) +@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=[]) @@ -1136,7 +1149,7 @@ def test_radio_buttons(fig_test, fig_ref): ax.text(.25, 1/3, "coffee", transform=ax.transAxes, va="center") -@check_figures_equal(extensions=['png']) +@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} @@ -1151,6 +1164,29 @@ def test_radio_buttons_props(fig_test, fig_ref): cb.set_radio_props({**radio_props, 's': (24 / 2)**2}) +@image_comparison(['check_radio_grid_buttons.png'], style='mpl20', remove_text=True) +def test_radio_grid_buttons(): + fig = plt.figure() + rb_horizontal = widgets.RadioButtons( + fig.add_axes((0.1, 0.05, 0.65, 0.05)), + ["tea", "coffee", "chocolate milk", "water", "soda", "coke"], + layout='horizontal', + active=4, + ) + cb_grid = widgets.CheckButtons( + fig.add_axes((0.1, 0.15, 0.25, 0.05*3)), + ["Chicken", "Salad", "Rice", "Sushi", "Pizza", "Fries"], + layout=(3, 2), + actives=[True, True, False, False, False, True], + ) + rb_vertical = widgets.RadioButtons( + fig.add_axes((0.1, 0.35, 0.2, 0.05*4)), + ["Trinity Cream", "Cake", "Ice Cream", "Muhallebi"], + layout='vertical', + active=3, + ) + + def test_radio_button_active_conflict(ax): with pytest.warns(UserWarning, match=r'Both the \*activecolor\* parameter'): @@ -1160,7 +1196,7 @@ def test_radio_button_active_conflict(ax): assert mcolors.same_color(rb._buttons.get_facecolor(), ['green', 'none']) -@check_figures_equal(extensions=['png']) +@check_figures_equal() def test_radio_buttons_activecolor_change(fig_test, fig_ref): widgets.RadioButtons(fig_ref.subplots(), ['tea', 'coffee'], activecolor='green') @@ -1171,7 +1207,7 @@ def test_radio_buttons_activecolor_change(fig_test, fig_ref): cb.activecolor = 'green' -@check_figures_equal(extensions=["png"]) +@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=[]) @@ -1183,7 +1219,7 @@ def test_check_buttons(fig_test, fig_ref): ax.text(.25, 1/3, "coffee", transform=ax.transAxes, va="center") -@check_figures_equal(extensions=['png']) +@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} @@ -1206,6 +1242,24 @@ def test_check_button_props(fig_test, fig_ref): cb.set_check_props({**check_props, 's': (24 / 2)**2}) +@pytest.mark.parametrize("widget", [widgets.RadioButtons, widgets.CheckButtons]) +def test__buttons_callbacks(ax, widget): + """Tests what https://github.com/matplotlib/matplotlib/pull/31031 fixed""" + on_clicked = mock.Mock(spec=noop, return_value=None) + button = widget(ax, ["Test Button"]) + button.on_clicked(on_clicked) + MouseEvent._from_ax_coords( + "button_press_event", + ax, + ax.transData.inverted().transform(ax.transAxes.transform( + # (x, y) of the 0th button defined at `_Buttons._init_layout` + (0.15, 0.5), + )), + 1, + )._process() + on_clicked.assert_called_once() + + def test_slider_slidermin_slidermax_invalid(): fig, ax = plt.subplots() # test min/max with floats @@ -1343,162 +1397,160 @@ def test_range_slider_same_init_values(orientation): assert_allclose(box.get_points().flatten()[idx], [0, 0.25, 0, 0.75]) -def check_polygon_selector(event_sequence, expected_result, selections_count, - **kwargs): +def check_polygon_selector(events, expected, selections_count, **kwargs): """ Helper function to test Polygon Selector. Parameters ---------- - event_sequence : list of tuples (etype, dict()) - A sequence of events to perform. The sequence is a list of tuples - where the first element of the tuple is an etype (e.g., 'onmove', - 'press', etc.), and the second element of the tuple is a dictionary of - the arguments for the event (e.g., xdata=5, key='shift', etc.). - expected_result : list of vertices (xdata, ydata) - The list of vertices that are expected to result from the event - sequence. + events : list[MouseEvent] + A sequence of events to perform. + expected : list of vertices (xdata, ydata) + The list of vertices expected to result from the event sequence. selections_count : int Wait for the tool to call its `onselect` function `selections_count` - times, before comparing the result to the `expected_result` + times, before comparing the result to the `expected` **kwargs Keyword arguments are passed to PolygonSelector. """ - ax = get_ax() - onselect = mock.Mock(spec=noop, return_value=None) + ax = events[0].canvas.figure.axes[0] tool = widgets.PolygonSelector(ax, onselect=onselect, **kwargs) - for (etype, event_args) in event_sequence: - do_event(tool, etype, **event_args) + for event in events: + event._process() assert onselect.call_count == selections_count - assert onselect.call_args == ((expected_result, ), {}) + assert onselect.call_args == ((expected, ), {}) -def polygon_place_vertex(xdata, ydata): - return [('onmove', dict(xdata=xdata, ydata=ydata)), - ('press', dict(xdata=xdata, ydata=ydata)), - ('release', dict(xdata=xdata, ydata=ydata))] +def polygon_place_vertex(ax, xy): + return [ + MouseEvent._from_ax_coords("motion_notify_event", ax, xy), + MouseEvent._from_ax_coords("button_press_event", ax, xy, 1), + MouseEvent._from_ax_coords("button_release_event", ax, xy, 1), + ] -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))] +def polygon_remove_vertex(ax, xy): + return [ + MouseEvent._from_ax_coords("motion_notify_event", ax, xy), + MouseEvent._from_ax_coords("button_press_event", ax, xy, 3), + MouseEvent._from_ax_coords("button_release_event", ax, xy, 3), + ] @pytest.mark.parametrize('draw_bounding_box', [False, True]) -def test_polygon_selector(draw_bounding_box): +def test_polygon_selector(ax, 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), + *polygon_place_vertex(ax, (50, 50)), + *polygon_place_vertex(ax, (150, 50)), + *polygon_place_vertex(ax, (50, 150)), + *polygon_place_vertex(ax, (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), + *polygon_place_vertex(ax, (50, 50)), + *polygon_place_vertex(ax, (150, 50)), + KeyEvent("key_press_event", ax.figure.canvas, "control"), + MouseEvent._from_ax_coords("motion_notify_event", ax, (50, 50)), + MouseEvent._from_ax_coords("button_press_event", ax, (50, 50), 1), + MouseEvent._from_ax_coords("motion_notify_event", ax, (75, 50)), + MouseEvent._from_ax_coords("button_release_event", ax, (75, 50), 1), + KeyEvent("key_release_event", ax.figure.canvas, "control"), + *polygon_place_vertex(ax, (50, 150)), + *polygon_place_vertex(ax, (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), + *polygon_place_vertex(ax, (50, 50)), + *polygon_place_vertex(ax, (150, 50)), + KeyEvent("key_press_event", ax.figure.canvas, "shift"), + MouseEvent._from_ax_coords("motion_notify_event", ax, (100, 100)), + MouseEvent._from_ax_coords("button_press_event", ax, (100, 100), 1), + MouseEvent._from_ax_coords("motion_notify_event", ax, (100, 125)), + MouseEvent._from_ax_coords("button_release_event", ax, (100, 125), 1), + KeyEvent("key_release_event", ax.figure.canvas, "shift"), + *polygon_place_vertex(ax, (50, 150)), + *polygon_place_vertex(ax, (50, 75)), ] check_selector(event_sequence, expected_result, 1) # Move first vertex after completing the polygon. - expected_result = [(75, 50), (150, 50), (50, 150)] + expected_result = [(85, 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)), + *polygon_place_vertex(ax, (60, 50)), + *polygon_place_vertex(ax, (150, 50)), + *polygon_place_vertex(ax, (50, 150)), + *polygon_place_vertex(ax, (60, 50)), + MouseEvent._from_ax_coords("motion_notify_event", ax, (60, 50)), + MouseEvent._from_ax_coords("button_press_event", ax, (60, 50), 1), + MouseEvent._from_ax_coords("motion_notify_event", ax, (85, 50)), + MouseEvent._from_ax_coords("button_release_event", ax, (85, 50), 1), ] 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')), + *polygon_place_vertex(ax, (50, 50)), + *polygon_place_vertex(ax, (150, 50)), + *polygon_place_vertex(ax, (50, 150)), + *polygon_place_vertex(ax, (50, 50)), + KeyEvent("key_press_event", ax.figure.canvas, "shift"), + MouseEvent._from_ax_coords("motion_notify_event", ax, (100, 100)), + MouseEvent._from_ax_coords("button_press_event", ax, (100, 100), 1), + MouseEvent._from_ax_coords("motion_notify_event", ax, (125, 125)), + MouseEvent._from_ax_coords("button_release_event", ax, (125, 125), 1), + KeyEvent("key_release_event", ax.figure.canvas, "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), + KeyEvent("key_press_event", ax.figure.canvas, "control"), + MouseEvent._from_ax_coords("motion_notify_event", ax, (100, 100)), + MouseEvent._from_ax_coords("button_press_event", ax, (100, 100), 1), + MouseEvent._from_ax_coords("motion_notify_event", ax, (125, 125)), + MouseEvent._from_ax_coords("button_release_event", ax, (125, 125), 1), + KeyEvent("key_release_event", ax.figure.canvas, "control"), + KeyEvent("key_press_event", ax.figure.canvas, "shift"), + MouseEvent._from_ax_coords("motion_notify_event", ax, (100, 100)), + MouseEvent._from_ax_coords("button_press_event", ax, (100, 100), 1), + MouseEvent._from_ax_coords("motion_notify_event", ax, (125, 125)), + MouseEvent._from_ax_coords("button_release_event", ax, (125, 125), 1), + KeyEvent("key_release_event", ax.figure.canvas, "shift"), + *polygon_place_vertex(ax, (50, 50)), + *polygon_place_vertex(ax, (150, 50)), + *polygon_place_vertex(ax, (50, 150)), + *polygon_place_vertex(ax, (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), + *polygon_place_vertex(ax, (50, 50)), + *polygon_place_vertex(ax, (250, 50)), + KeyEvent("key_press_event", ax.figure.canvas, "escape"), + KeyEvent("key_release_event", ax.figure.canvas, "escape"), + *polygon_place_vertex(ax, (50, 50)), + *polygon_place_vertex(ax, (150, 50)), + *polygon_place_vertex(ax, (50, 150)), + *polygon_place_vertex(ax, (50, 50)), ] check_selector(event_sequence, expected_result, 1) @@ -1510,15 +1562,13 @@ def test_polygon_selector_set_props_handle_props(ax, draw_bounding_box): 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) + for event in [ + *polygon_place_vertex(ax, (50, 50)), + *polygon_place_vertex(ax, (150, 50)), + *polygon_place_vertex(ax, (50, 150)), + *polygon_place_vertex(ax, (50, 50)), + ]: + event._process() artist = tool._selection_artist assert artist.get_color() == 'b' @@ -1549,17 +1599,17 @@ def test_rect_visibility(fig_test, fig_ref): # 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): +def test_polygon_selector_remove(ax, 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]), + event_sequence = [polygon_place_vertex(ax, verts[0]), + polygon_place_vertex(ax, verts[1]), + polygon_place_vertex(ax, verts[2]), # Finish the polygon - polygon_place_vertex(*verts[0])] + polygon_place_vertex(ax, verts[0])] # Add an extra point - event_sequence.insert(idx, polygon_place_vertex(200, 200)) + event_sequence.insert(idx, polygon_place_vertex(ax, (200, 200))) # Remove the extra point - event_sequence.append(polygon_remove_vertex(200, 200)) + event_sequence.append(polygon_remove_vertex(ax, (200, 200))) # Flatten list of lists event_sequence = functools.reduce(operator.iadd, event_sequence, []) check_polygon_selector(event_sequence, verts, 2, @@ -1567,14 +1617,14 @@ def test_polygon_selector_remove(idx, draw_bounding_box): @pytest.mark.parametrize('draw_bounding_box', [False, True]) -def test_polygon_selector_remove_first_point(draw_bounding_box): +def test_polygon_selector_remove_first_point(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_remove_vertex(*verts[0]), + *polygon_place_vertex(ax, verts[0]), + *polygon_place_vertex(ax, verts[1]), + *polygon_place_vertex(ax, verts[2]), + *polygon_place_vertex(ax, verts[0]), + *polygon_remove_vertex(ax, verts[0]), ] check_polygon_selector(event_sequence, verts[1:], 2, draw_bounding_box=draw_bounding_box) @@ -1584,27 +1634,27 @@ def test_polygon_selector_remove_first_point(draw_bounding_box): 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_place_vertex(ax, verts[0]), + *polygon_place_vertex(ax, verts[1]), + *polygon_place_vertex(ax, verts[2]), + *polygon_place_vertex(ax, verts[0]), # Polygon completed, now remove first two verts. - *polygon_remove_vertex(*verts[1]), - *polygon_remove_vertex(*verts[2]), + *polygon_remove_vertex(ax, verts[1]), + *polygon_remove_vertex(ax, verts[2]), # At this point the tool should be reset so we can add more vertices. - *polygon_place_vertex(*verts[1]), + *polygon_place_vertex(ax, 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) + for event in event_sequence: + event._process() # After removing two verts, only one remains, and the - # selector should be automatically resete + # selector should be automatically reset assert tool.verts == verts[0:2] @pytest.mark.parametrize('draw_bounding_box', [False, True]) -@check_figures_equal(extensions=['png']) +@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() @@ -1615,14 +1665,13 @@ def test_polygon_selector_verts_setter(fig_test, fig_ref, draw_bounding_box): 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) + for event in [ + *polygon_place_vertex(ax_ref, verts[0]), + *polygon_place_vertex(ax_ref, verts[1]), + *polygon_place_vertex(ax_ref, verts[2]), + *polygon_place_vertex(ax_ref, verts[0]), + ]: + event._process() def test_polygon_selector_box(ax): @@ -1630,40 +1679,29 @@ def test_polygon_selector_box(ax): 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]), + *polygon_place_vertex(ax, verts[0]), + *polygon_place_vertex(ax, verts[1]), + *polygon_place_vertex(ax, verts[2]), + *polygon_place_vertex(ax, verts[3]), + *polygon_place_vertex(ax, 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 + for event in event_sequence: + event._process() # 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() + MouseEvent._from_ax_coords("button_press_event", ax, (40, 40), 1)._process() + MouseEvent._from_ax_coords("motion_notify_event", ax, (20, 20))._process() + MouseEvent._from_ax_coords("button_release_event", ax, (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() + MouseEvent._from_ax_coords("button_press_event", ax, (10, 10), 1)._process() + MouseEvent._from_ax_coords("motion_notify_event", ax, (30, 30))._process() + MouseEvent._from_ax_coords("button_release_event", ax, (30, 30), 1)._process() np.testing.assert_allclose( tool.verts, [(30, 20), (20, 30), (30, 40), (40, 30)]) @@ -1671,10 +1709,8 @@ def test_polygon_selector_box(ax): 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() + MouseEvent._from_ax_coords("button_press_event", ax, (30, 20), 3)._process() + MouseEvent._from_ax_coords("button_release_event", ax, (30, 20), 3)._process() np.testing.assert_allclose( tool.verts, [(20, 30), (30, 40), (40, 30)]) np.testing.assert_allclose( @@ -1687,9 +1723,9 @@ def test_polygon_selector_clear_method(ax): 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) + for xy in result: + for event in polygon_place_vertex(ax, xy): + event._process() artist = tool._selection_artist @@ -1706,25 +1742,28 @@ def test_polygon_selector_clear_method(ax): @pytest.mark.parametrize("horizOn", [False, True]) @pytest.mark.parametrize("vertOn", [False, True]) -def test_MultiCursor(horizOn, vertOn): +@pytest.mark.parametrize("with_deprecated_canvas", [False, True]) +def test_MultiCursor(horizOn, vertOn, with_deprecated_canvas): 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 - ) + if with_deprecated_canvas: + with pytest.warns(mpl.MatplotlibDeprecationWarning, match=r"canvas.*deprecat"): + multi = widgets.MultiCursor( + None, (ax1, ax2), useblit=False, horizOn=horizOn, vertOn=vertOn + ) + else: + # useblit=false to avoid having to draw the figure to cache the renderer + multi = widgets.MultiCursor( + (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) + MouseEvent._from_ax_coords("motion_notify_event", ax1, (.5, .25))._process() # force a draw + draw event to exercise clear fig.canvas.draw() @@ -1742,8 +1781,7 @@ def test_MultiCursor(horizOn, vertOn): # 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) + MouseEvent._from_ax_coords("motion_notify_event", ax1, (.5, .25))._process() 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()]) == ( @@ -1751,9 +1789,31 @@ def test_MultiCursor(horizOn, vertOn): # 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) + MouseEvent._from_ax_coords("motion_notify_event", ax3, (.75, .75))._process() for l in multi.vlines: assert l.get_xdata() == (.5, .5) for l in multi.hlines: assert l.get_ydata() == (.25, .25) + + +def test_parent_axes_removal(): + + fig, (ax_radio, ax_checks) = plt.subplots(1, 2) + + radio = widgets.RadioButtons(ax_radio, ['1', '2'], 0) + checks = widgets.CheckButtons(ax_checks, ['1', '2'], [True, False]) + + ax_checks.remove() + ax_radio.remove() + with io.BytesIO() as out: + # verify that saving does not raise + fig.savefig(out, format='raw') + + # verify that this method which is triggered by a draw_event callback when + # blitting is enabled does not raise. Calling private methods is simpler + # than trying to force blitting to be enabled with Agg or use a GUI + # framework. + renderer = fig._get_renderer() + evt = DrawEvent('draw_event', fig.canvas, renderer) + radio._clear(evt) + checks._clear(evt) diff --git a/lib/matplotlib/tests/tinypages/.gitignore b/lib/matplotlib/tests/tinypages/.gitignore deleted file mode 100644 index 69fa449dd96e..000000000000 --- a/lib/matplotlib/tests/tinypages/.gitignore +++ /dev/null @@ -1 +0,0 @@ -_build/ diff --git a/lib/matplotlib/texmanager.py b/lib/matplotlib/texmanager.py index a374bfba8cab..2a7201c87d77 100644 --- a/lib/matplotlib/texmanager.py +++ b/lib/matplotlib/texmanager.py @@ -23,7 +23,6 @@ import functools import hashlib import logging -import os from pathlib import Path import subprocess from tempfile import TemporaryDirectory @@ -63,10 +62,17 @@ class TexManager: Repeated calls to this constructor always return the same instance. """ - _texcache = os.path.join(mpl.get_cachedir(), 'tex.cache') + _cache_dir = Path(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}', @@ -80,13 +86,10 @@ class TexManager: 'helvetica': r'\usepackage{helvet}', 'avant garde': r'\usepackage{avant}', 'courier': 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': r'\usepackage{type1ec}', - 'computer modern roman': r'\usepackage{type1ec}', - 'computer modern sans serif': r'\usepackage{type1ec}', - 'computer modern typewriter': r'\usepackage{type1ec}', + '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', @@ -105,7 +108,7 @@ class TexManager: @functools.lru_cache # Always return the same instance. def __new__(cls): - Path(cls._texcache).mkdir(parents=True, exist_ok=True) + cls._cache_dir.mkdir(parents=True, exist_ok=True) return object.__new__(cls) @classmethod @@ -163,20 +166,30 @@ def _get_font_preamble_and_command(cls): return preamble, fontcmd @classmethod - def get_basefile(cls, tex, fontsize, dpi=None): + def _get_base_path(cls, tex, fontsize, dpi=None): """ - Return a filename based on a hash of the string, fontsize, and dpi. + Return a file path based on a hash of the string, fontsize, and dpi. """ src = cls._get_tex_source(tex, fontsize) + str(dpi) - filehash = hashlib.md5(src.encode('utf-8')).hexdigest() - filepath = Path(cls._texcache) + filehash = hashlib.sha256( + src.encode('utf-8'), + usedforsecurity=False + ).hexdigest() + filepath = cls._cache_dir 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 = filepath / filehash[i:i+2] filepath.mkdir(parents=True, exist_ok=True) - return os.path.join(filepath, filehash) + return filepath / filehash + + @classmethod + def get_basefile(cls, tex, fontsize, dpi=None): # Kept for backcompat. + """ + Return a filename based on a hash of the string, fontsize, and dpi. + """ + return str(cls._get_base_path(tex, fontsize, dpi)) @classmethod def get_font_preamble(cls): @@ -197,6 +210,7 @@ def _get_tex_source(cls, tex, fontsize): font_preamble, fontcmd = cls._get_font_preamble_and_command() baselineskip = 1.25 * fontsize return "\n".join([ + r"\RequirePackage{fix-cm}", r"\documentclass{article}", r"% Pass-through \mathdefault, which is used in non-usetex mode", r"% to use the default text font but was historically suppressed", @@ -220,8 +234,6 @@ def _get_tex_source(cls, tex, fontsize): 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}}}%", @@ -235,22 +247,17 @@ def make_tex(cls, tex, fontsize): Return the file name. """ - texfile = cls.get_basefile(tex, fontsize) + ".tex" - Path(texfile).write_text(cls._get_tex_source(tex, fontsize), - encoding='utf-8') - return texfile + texpath = cls._get_base_path(tex, fontsize).with_suffix(".tex") + texpath.write_text(cls._get_tex_source(tex, fontsize), encoding='utf-8') + return str(texpath) @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 cls._texcache, + command, cwd=cwd if cwd is not None else cls._cache_dir, stderr=subprocess.STDOUT) - except FileNotFoundError as exc: - raise RuntimeError( - 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' @@ -263,6 +270,10 @@ def _run_checked_subprocess(cls, command, tex, *, cwd=None): tex=tex.encode('unicode_escape'), exc=exc.output.decode('utf-8', 'backslashreplace')) ) from None + except (FileNotFoundError, OSError) as exc: + raise RuntimeError( + f'Failed to process string with tex because {command[0]} ' + 'could not be found') from exc _log.debug(report) return report @@ -273,11 +284,9 @@ def make_dvi(cls, tex, fontsize): Return the file name. """ - basefile = cls.get_basefile(tex, fontsize) - dvifile = '%s.dvi' % basefile - if not os.path.exists(dvifile): - texfile = Path(cls.make_tex(tex, fontsize)) - # Generate the dvi in a temporary directory to avoid race + dvipath = cls._get_base_path(tex, fontsize).with_suffix(".dvi") + if not dvipath.exists(): + # Generate the tex and 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, @@ -286,15 +295,17 @@ def make_dvi(cls, tex, fontsize): # 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) + with TemporaryDirectory(dir=dvipath.parent) as tmpdir: + Path(tmpdir, "file.tex").write_text( + cls._get_tex_source(tex, fontsize), encoding='utf-8') cls._run_checked_subprocess( - ["latex", "-interaction=nonstopmode", "--halt-on-error", - f"--output-directory={tmppath.name}", - f"{texfile.name}"], tex, cwd=cwd) - (tmppath / Path(dvifile).name).replace(dvifile) - return dvifile + ["latex", "-interaction=nonstopmode", "-halt-on-error", + "-no-shell-escape", "file.tex"], tex, cwd=tmpdir) + Path(tmpdir, "file.dvi").replace(dvipath) + # Also move the tex source to the main cache directory, but + # only for backcompat. + Path(tmpdir, "file.tex").replace(dvipath.with_suffix(".tex")) + return str(dvipath) @classmethod def make_png(cls, tex, fontsize, dpi): @@ -303,35 +314,33 @@ def make_png(cls, tex, fontsize, dpi): Return the file name. """ - 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 = 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").raw_version != "1.16"): - cmd.insert(1, "--freetype0") - cls._run_checked_subprocess(cmd, tex) - return pngfile + pngpath = cls._get_base_path(tex, fontsize, dpi).with_suffix(".png") + if not pngpath.exists(): + dvipath = cls.make_dvi(tex, fontsize) + with TemporaryDirectory(dir=pngpath.parent) as tmpdir: + cmd = ["dvipng", "-bg", "Transparent", "-D", str(dpi), + "-T", "tight", "-o", "file.png", dvipath] + # 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").raw_version != "1.16"): + cmd.insert(1, "--freetype0") + cls._run_checked_subprocess(cmd, tex, cwd=tmpdir) + Path(tmpdir, "file.png").replace(pngpath) + return str(pngpath) @classmethod def get_grey(cls, tex, fontsize=None, dpi=None): """Return the alpha channel.""" - if not fontsize: - fontsize = mpl.rcParams['font.size'] - if not dpi: - dpi = mpl.rcParams['savefig.dpi'] + 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 = cls.make_png(tex, fontsize, dpi) - rgba = mpl.image.imread(os.path.join(cls._texcache, pngfile)) + rgba = mpl.image.imread(pngfile) cls._grey_arrayd[key] = alpha = rgba[:, :, -1] return alpha @@ -357,9 +366,9 @@ 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) + dvipath = cls.make_dvi(tex, fontsize) dpi_fraction = renderer.points_to_pixels(1.) if renderer else 1 - with dviread.Dvi(dvifile, 72 * dpi_fraction) as dvi: + with dviread.Dvi(dvipath, 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/text.py b/lib/matplotlib/text.py index 0b65450f760b..2e870f050209 100644 --- a/lib/matplotlib/text.py +++ b/lib/matplotlib/text.py @@ -11,7 +11,7 @@ import numpy as np import matplotlib as mpl -from . import _api, artist, cbook, _docstring +from . import _api, artist, cbook, _docstring, colors as mcolors from .artist import Artist from .font_manager import FontProperties from .patches import FancyArrowPatch, FancyBboxPatch, Rectangle @@ -33,7 +33,7 @@ def _get_textbox(text, renderer): # TODO : This function may move into the Text class as a method. As a # 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 + # called within the _get_textbox. So, it would be better to move this # function as a method with some refactoring of _get_layout method. projected_xs = [] @@ -64,17 +64,89 @@ def _get_textbox(text, renderer): 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) + # hit the outer cache layer and get the function to compute the metrics + # for this renderer instance + get_text_metrics = _get_text_metrics_function(renderer) + # call the function to compute the metrics and return + # + # We pass a copy of the fontprop because FontProperties is both mutable and + # has a `__hash__` that depends on that mutable state. This is not ideal + # as it means the hash of an object is not stable over time which leads to + # very confusing behavior when used as keys in dictionaries or hashes. + return get_text_metrics(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) + +def _get_text_metrics_function(input_renderer, _cache=weakref.WeakKeyDictionary()): + """ + Helper function to provide a two-layered cache for font metrics + + + To get the rendered size of a size of string we need to know: + - what renderer we are using + - the current dpi of the renderer + - the string + - the font properties + - is it math text or not + + We do this as a two-layer cache with the outer layer being tied to a + renderer instance and the inner layer handling everything else. + + The outer layer is implemented as `.WeakKeyDictionary` keyed on the + renderer. As long as someone else is holding a hard ref to the renderer + we will keep the cache alive, but it will be automatically dropped when + the renderer is garbage collected. + + The inner layer is provided by an lru_cache with a large maximum size (such + that we expect very few cache misses in actual use cases). As the + dpi is mutable on the renderer, we need to explicitly include it as part of + the cache key on the inner layer even though we do not directly use it (it is + used in the method call on the renderer). + + This function takes a renderer and returns a function that can be used to + get the font metrics. + + Parameters + ---------- + input_renderer : maplotlib.backend_bases.RendererBase + The renderer to set the cache up for. + + _cache : dict, optional + We are using the mutable default value to attach the cache to the function. + + In principle you could pass a different dict-like to this function to inject + a different cache, but please don't. This is an internal function not meant to + be reused outside of the narrow context we need it for. + + There is a possible race condition here between threads, we may need to drop the + mutable default and switch to a threadlocal variable in the future. + + """ + if (_text_metrics := _cache.get(input_renderer, None)) is None: + # We are going to include this in the closure we put as values in the + # cache. Closing over a hard-ref would create an unbreakable reference + # cycle. + renderer_ref = weakref.ref(input_renderer) + + # define the function locally to get a new lru_cache per renderer + @functools.lru_cache(4096) + # dpi is unused, but participates in cache invalidation (via the renderer). + def _text_metrics(text, fontprop, ismath, dpi): + # this should never happen under normal use, but this is a better error to + # raise than an AttributeError on `None` + if (local_renderer := renderer_ref()) is None: + raise RuntimeError( + "Trying to get text metrics for a renderer that no longer exists. " + "This should never happen and is evidence of a bug elsewhere." + ) + # do the actual method call we need and return the result + return local_renderer.get_text_width_height_descent(text, fontprop, ismath) + + # stash the function for later use. + _cache[input_renderer] = _text_metrics + + # return the inner function + return _text_metrics @_docstring.interpd @@ -188,8 +260,7 @@ def _reset_visual_defaults( linespacing = 1.2 # Maybe use rcParam later. self.set_linespacing(linespacing) self.set_rotation_mode(rotation_mode) - self.set_antialiased(antialiased if antialiased is not None else - mpl.rcParams['text.antialiased']) + self.set_antialiased(mpl._val_or_rc(antialiased, 'text.antialiased')) def update(self, kwargs): # docstring inherited @@ -301,16 +372,19 @@ def set_rotation_mode(self, m): Parameters ---------- - m : {None, 'default', 'anchor'} + 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. Passing ``None`` will set the rotation - mode to ``"default"``. + 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"), rotation_mode=m) + _api.check_in_list(("anchor", "default", "xtick", "ytick"), rotation_mode=m) self._rotation_mode = m self.stale = True @@ -454,6 +528,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': @@ -509,14 +588,21 @@ def _get_layout(self, renderer): 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 -------- :: @@ -551,6 +637,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 @@ -678,10 +766,10 @@ 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(), - cbook.is_math_text(text)) + 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): @@ -776,7 +864,7 @@ def draw(self, renderer): self._bbox_patch.draw(renderer) gc = renderer.new_gc() - gc.set_foreground(self.get_color()) + gc.set_foreground(mcolors.to_rgba(self.get_color()), isRGBA=True) gc.set_alpha(self.get_alpha()) gc.set_url(self._url) gc.set_antialiased(self._antialiased) @@ -972,9 +1060,21 @@ def get_window_extent(self, renderer=None, dpi=None): bbox = bbox.translated(x, y) return bbox + def get_tightbbox(self, renderer=None): + # Exclude text at data coordinates outside the valid domain of the axes + # scales (e.g., negative coordinates with a log scale). + if (self.axes + and self.get_transform() == self.axes.transData + and not self.axes._point_in_data_domain(*self.get_unitless_position())): + return Bbox.null() + return super().get_tightbbox(renderer) + 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 ---------- @@ -1336,10 +1436,7 @@ def set_usetex(self, usetex): Whether to render using TeX, ``None`` means to use :rc:`text.usetex`. """ - if usetex is None: - self._usetex = mpl.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): @@ -1380,6 +1477,32 @@ def set_fontname(self, 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' + + 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: """Callable helper class for working with `Annotation`.""" @@ -1511,9 +1634,7 @@ def _get_xy_transform(self, renderer, coords): return self.axes.transData elif coords == 'polar': from matplotlib.projections import PolarAxes - tr = PolarAxes.PolarTransform(apply_theta_transforms=False) - trans = tr + self.axes.transData - return trans + return PolarAxes.PolarTransform() + self.axes.transData try: bbox_name, unit = coords.split() diff --git a/lib/matplotlib/text.pyi b/lib/matplotlib/text.pyi index d65a3dc4c7da..7223693945ec 100644 --- a/lib/matplotlib/text.pyi +++ b/lib/matplotlib/text.pyi @@ -2,7 +2,7 @@ from .artist import Artist from .backend_bases import RendererBase from .font_manager import FontProperties from .offsetbox import DraggableAnnotation -from .path import Path +from pathlib import Path from .patches import FancyArrowPatch, FancyBboxPatch from .textpath import ( # noqa: F401, reexported API TextPath as TextPath, @@ -14,7 +14,7 @@ from .transforms import ( Transform, ) -from collections.abc import Callable, Iterable +from collections.abc import Iterable from typing import Any, Literal from .typing import ColorType, CoordsType @@ -46,9 +46,9 @@ class Text(Artist): 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"]) -> None: ... - def get_rotation_mode(self) -> Literal["default", "anchor"]: ... - def set_bbox(self, rectprops: dict[str, Any]) -> None: ... + 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: ... @@ -106,6 +106,8 @@ class Text(Artist): 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__( diff --git a/lib/matplotlib/textpath.py b/lib/matplotlib/textpath.py index 83182e3f5400..8deae19c42e7 100644 --- a/lib/matplotlib/textpath.py +++ b/lib/matplotlib/textpath.py @@ -232,23 +232,16 @@ def get_glyphs_tex(self, prop, s, glyph_map=None, # Gather font information and do some setup for combining # characters into strings. + t1_encodings = {} for text in page.text: - font = get_font(text.font_path) + font = get_font(text.font.resolve_path()) + if text.font.subfont: + raise NotImplementedError("Indexing TTC fonts is not supported yet") 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) - glyph_name_or_index = text.glyph_name_or_index - if isinstance(glyph_name_or_index, str): - index = font.get_name_index(glyph_name_or_index) - font.load_glyph(index, flags=LoadFlags.TARGET_LIGHT) - elif isinstance(glyph_name_or_index, int): - self._select_native_charmap(font) - font.load_char( - glyph_name_or_index, flags=LoadFlags.TARGET_LIGHT) - else: # Should not occur. - raise TypeError(f"Glyph spec of unexpected type: " - f"{glyph_name_or_index!r}") + font.load_glyph(text.index, flags=LoadFlags.TARGET_LIGHT) glyph_map_new[char_id] = font.get_path() glyph_ids.append(char_id) @@ -269,23 +262,6 @@ def get_glyphs_tex(self, prop, s, glyph_map=None, return (list(zip(glyph_ids, xpositions, ypositions, sizes)), glyph_map_new, myrects) - @staticmethod - def _select_native_charmap(font): - # Select the native charmap. (we can't directly identify it but it's - # typically an Adobe charmap). - 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).", font.fname) - text_to_path = TextToPath() diff --git a/lib/matplotlib/ticker.py b/lib/matplotlib/ticker.py index 0053031ece3e..83e13841677a 100644 --- a/lib/matplotlib/ticker.py +++ b/lib/matplotlib/ticker.py @@ -407,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 ----- @@ -444,13 +449,12 @@ class ScalarFormatter(Formatter): """ - 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'] + self.set_usetex(usetex) self.set_useMathText(useMathText) self.orderOfMagnitude = 0 self.format = '' @@ -458,6 +462,16 @@ def __init__(self, useOffset=None, useMathText=None, useLocale=None): self._powerlimits = mpl.rcParams['axes.formatter.limits'] 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): """ Return whether automatic mode for offset notation is active. @@ -498,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: @@ -526,10 +540,7 @@ 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) @@ -847,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`` @@ -884,12 +898,9 @@ 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, @@ -957,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]: @@ -987,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 @@ -1014,7 +1029,7 @@ def __call__(self, x, pos=None): return '' vmin, vmax = self.axis.get_view_interval() - vmin, vmax = mtransforms.nonsingular(vmin, vmax, expander=0.05) + vmin, vmax = mtransforms._nonsingular(vmin, vmax, expander=0.05) s = self._num_to_string(x, vmin, vmax) return self.fix_minus(s) @@ -1053,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 @@ -1324,7 +1338,7 @@ def format_data_short(self, value): 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. @@ -1356,7 +1370,7 @@ class EngFormatter(Formatter): } def __init__(self, unit="", places=None, sep=" ", *, usetex=None, - useMathText=None): + useMathText=None, useOffset=False): r""" Parameters ---------- @@ -1390,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 = f"{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 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'). @@ -1468,13 +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 = f"${mant:{fmt}}${self.sep}{prefix}" + return f"${mant:{fmt}}${suffix}" else: - formatted = f"{mant:{fmt}}{self.sep}{prefix}" - - return formatted + return f"{mant:{fmt}}{suffix}" class PercentFormatter(Formatter): @@ -1666,7 +1730,7 @@ def nonsingular(self, v0, v1): default view limits. - Otherwise, ``(v0, v1)`` is returned without modification. """ - return mtransforms.nonsingular(v0, v1, expander=.05) + return mtransforms._nonsingular(v0, v1, expander=.05) def view_limits(self, vmin, vmax): """ @@ -1674,7 +1738,7 @@ def view_limits(self, vmin, vmax): Subclasses should override this method to change locator behaviour. """ - return mtransforms.nonsingular(vmin, vmax) + return mtransforms._nonsingular(vmin, vmax) class IndexLocator(Locator): @@ -1684,6 +1748,7 @@ class IndexLocator(Locator): 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 @@ -1702,8 +1767,11 @@ def __call__(self): return self.tick_values(dmin, dmax) def tick_values(self, vmin, vmax): - return self.raise_if_exceeds( - np.arange(vmin + self.offset, vmax + 1, self._base)) + # We want tick values in the closed interval [vmin, vmax]. + # Since np.arange(start, stop) returns values in the semi-open interval + # [start, stop), we add a minimal offset so that stop = vmax + eps + tick_values = np.arange(vmin + self.offset, vmax + 1e-12, self._base) + return self.raise_if_exceeds(tick_values) class FixedLocator(Locator): @@ -1736,9 +1804,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 @@ -1753,7 +1819,7 @@ def tick_values(self, vmin, vmax): class NullLocator(Locator): """ - No ticks + Place no ticks. """ def __call__(self): @@ -1765,8 +1831,7 @@ 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 [] @@ -1775,12 +1840,11 @@ class LinearLocator(Locator): """ 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 so that interactive navigation will - be nice - + 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): """ Parameters @@ -1820,7 +1884,7 @@ def __call__(self): return self.tick_values(vmin, vmax) def tick_values(self, vmin, vmax): - vmin, vmax = mtransforms.nonsingular(vmin, vmax, expander=0.05) + vmin, vmax = mtransforms._nonsingular(vmin, vmax, expander=0.05) if (vmin, vmax) in self.presets: return self.presets[(vmin, vmax)] @@ -1849,7 +1913,7 @@ def view_limits(self, vmin, vmax): vmin = math.floor(scale * vmin) / scale vmax = math.ceil(scale * vmax) / scale - return mtransforms.nonsingular(vmin, vmax) + return mtransforms._nonsingular(vmin, vmax) class MultipleLocator(Locator): @@ -1919,7 +1983,7 @@ def view_limits(self, dmin, dmax): vmin = dmin vmax = dmax - return mtransforms.nonsingular(vmin, vmax) + return mtransforms._nonsingular(vmin, vmax) def scale_range(vmin, vmax, n=1, threshold=100): @@ -1940,6 +2004,7 @@ class _Edge_integer: Take floating-point precision limitations into account when calculating tick locations as integer multiples of a step. """ + def __init__(self, step, offset): """ Parameters @@ -2174,7 +2239,7 @@ def tick_values(self, vmin, vmax): if self._symmetric: vmax = max(abs(vmin), abs(vmax)) vmin = -vmax - vmin, vmax = mtransforms.nonsingular( + vmin, vmax = mtransforms._nonsingular( vmin, vmax, expander=1e-13, tiny=1e-14) locs = self._raw_ticks(vmin, vmax) @@ -2192,7 +2257,7 @@ def view_limits(self, dmin, dmax): dmax = max(abs(dmin), abs(dmax)) dmin = -dmax - dmin, dmax = mtransforms.nonsingular( + dmin, dmax = mtransforms._nonsingular( dmin, dmax, expander=1e-12, tiny=1e-13) if mpl.rcParams['axes.autolimit_mode'] == 'round_numbers': @@ -2341,14 +2406,19 @@ def __call__(self): 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 if vmin <= 0.0: @@ -2359,17 +2429,17 @@ def tick_values(self, vmin, vmax): raise ValueError( "Data has no positive values, and therefore cannot be log-scaled.") - _log.debug('vmin %s vmax %s', vmin, vmax) - 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): - 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: @@ -2380,41 +2450,87 @@ def tick_values(self, vmin, vmax): 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 // numticks + 1) - - # if we have decided that the stride is as big or bigger than - # the range, clip the stride back to the available range - 1 - # with a floor of 1. This prevents getting axis with only 1 tick - # visible. - if stride >= numdec: - stride = max(1, numdec - 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 have_subs: - if stride == 1: - ticklocs = np.concatenate( - [subs * decade_start for decade_start in b ** decades]) + # 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: + # *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: + 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 ** decades + ticklocs = b ** np.array(decades) - _log.debug('ticklocs %r', ticklocs) if (len(subs) > 1 and stride == 1 - and ((vmin <= ticklocs) & (ticklocs <= vmax)).sum() <= 1): + and (len(decades) - 2 # major + + ((vmin <= ticklocs) & (ticklocs <= vmax)).sum()) # minor + <= 1): # If we're a minor locator *that expects at least two ticks per # decade* and the major locator stride is 1 and there's no more - # than one minor tick, switch to AutoLocator. + # than one major or minor tick, switch to AutoLocator. return AutoLocator().tick_values(vmin, vmax) else: return self.raise_if_exceeds(ticklocs) @@ -2605,7 +2721,7 @@ def view_limits(self, vmin, vmax): vmin = _decade_less(vmin, b) vmax = _decade_greater(vmax, b) - return mtransforms.nonsingular(vmin, vmax) + return mtransforms._nonsingular(vmin, vmax) class AsinhLocator(Locator): @@ -2873,20 +2989,21 @@ class AutoMinorLocator(Locator): Place evenly spaced minor ticks, with the step size and maximum number of ticks chosen automatically. - The Axis scale must be linear with evenly spaced major ticks . + 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. - - If *n* is omitted or None, the value stored in rcParams will be used. - In case *n* is set to '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. + 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 '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 diff --git a/lib/matplotlib/ticker.pyi b/lib/matplotlib/ticker.pyi index fd8e41848671..bed288658909 100644 --- a/lib/matplotlib/ticker.pyi +++ b/lib/matplotlib/ticker.pyi @@ -64,8 +64,16 @@ class ScalarFormatter(Formatter): 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 @@ -125,7 +133,7 @@ class LogitFormatter(Formatter): def set_minor_number(self, minor_number: int) -> None: ... def format_data_short(self, value: float) -> str: ... -class EngFormatter(Formatter): +class EngFormatter(ScalarFormatter): ENG_PREFIXES: dict[int, str] unit: str places: int | None @@ -137,20 +145,9 @@ class EngFormatter(Formatter): sep: str = ..., *, usetex: bool | None = ..., - useMathText: bool | None = ... + useMathText: bool | None = ..., + useOffset: bool | float | None = ..., ) -> None: ... - def get_usetex(self) -> bool: ... - def set_usetex(self, val: bool | None) -> None: ... - @property - def usetex(self) -> bool: ... - @usetex.setter - def usetex(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 format_eng(self, num: float) -> str: ... class PercentFormatter(Formatter): @@ -298,3 +295,14 @@ class AutoLocator(MaxNLocator): 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/transforms.py b/lib/matplotlib/transforms.py index 2934b0a77809..4e77020d3f8e 100644 --- a/lib/matplotlib/transforms.py +++ b/lib/matplotlib/transforms.py @@ -35,7 +35,6 @@ # `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 @@ -45,9 +44,8 @@ import numpy as np from numpy.linalg import inv -from matplotlib import _api -from matplotlib._path import ( - affine_transform, count_bboxes_overlapping_bbox, update_path_extents) +from matplotlib import _api, _docstring +from matplotlib._path import affine_transform, count_bboxes_overlapping_bbox from .path import Path DEBUG = False @@ -99,7 +97,6 @@ class TransformNode: # Some metadata about the transform, used to determine whether an # invalidation is affine-only is_affine = False - is_bbox = _api.deprecated("3.9")(_api.classproperty(lambda cls: False)) pass_through = False """ @@ -141,7 +138,9 @@ def __setstate__(self, data_dict): for k, v in self._parents.items() if v is not None} def __copy__(self): - other = copy.copy(super()) + cls = type(self) + other = cls.__new__(cls) + other.__dict__.update(self.__dict__) # 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 = {} @@ -217,7 +216,6 @@ class BboxBase(TransformNode): and height, but these are not stored explicitly. """ - is_bbox = _api.deprecated("3.9")(_api.classproperty(lambda cls: True)) is_affine = True if DEBUG: @@ -242,7 +240,7 @@ 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] @@ -252,7 +250,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] @@ -262,7 +260,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] @@ -272,7 +270,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] @@ -282,7 +280,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] @@ -292,7 +290,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] @@ -364,7 +362,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) @@ -376,6 +377,29 @@ def extents(self): def get_points(self): raise NotImplementedError + def _is_finite(self): + """ + Return whether the bounding box is finite and not degenerate to a + single point. + + We count the box as finite if neither width nor height are infinite + and at least one direction is non-zero; i.e. a point is not finite, + but a horizontal or vertical line is. + + .. versionadded:: 3.11 + + Notes + ----- + We keep this private for now because concise naming is hard and + because we are not sure how universal the concept is. It is + currently used only for filtering bboxes to be included in + tightbbox calculation, but I'm unsure whether single points + should be included there as well. + """ + width = self.width + height = self.height + return (width > 0 or height > 0) and width < np.inf and height < np.inf + def containsx(self, x): """ Return whether *x* is in the closed (:attr:`x0`, :attr:`x1`) interval. @@ -865,13 +889,39 @@ def update_from_path(self, path, ignore=None, updatex=True, updatey=True): 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 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] + minx = np.min(x, initial=np.inf) + points[0, 0] = min(points[0, 0], minx) + points[1, 0] = max(points[1, 0], np.max(x, initial=-np.inf)) + if minx > 0: # Fast path for all-positive x values + minpos[0] = min(minpos[0], minx) + else: + minpos[0] = min(minpos[0], np.min(x[x > 0], initial=np.inf)) + if updatey: + y = path.vertices[..., 1][valid_points] + miny = np.min(y, initial=np.inf) + points[0, 1] = min(points[0, 1], miny) + points[1, 1] = max(points[1, 1], np.max(y, initial=-np.inf)) + if miny > 0: # Fast path for all-positive y values + minpos[1] = min(minpos[1], miny) + else: + minpos[1] = min(minpos[1], np.min(y[y > 0], initial=np.inf)) - if changed: + if np.any(points != self._points) or np.any(minpos != self._minpos): self.invalidate() if updatex: self._points[:, 0] = points[:, 0] @@ -896,8 +946,9 @@ def update_from_data_x(self, x, ignore=None): - When ``None``, use the last value passed to :meth:`ignore`. """ x = np.ravel(x) - self.update_from_data_xy(np.column_stack([x, np.ones(x.size)]), - ignore=ignore, updatey=False) + # 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): """ @@ -915,8 +966,9 @@ def update_from_data_y(self, y, ignore=None): - When ``None``, use the last value passed to :meth:`ignore`. """ y = np.ravel(y) - self.update_from_data_xy(np.column_stack([np.ones(y.size), y]), - ignore=ignore, updatex=False) + # 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): """ @@ -1397,7 +1449,7 @@ def contains_branch(self, other): return True return False - def contains_branch_seperately(self, other_transform): + def contains_branch_separately(self, other_transform): """ Return whether the given branch is a sub-tree of this transform on each separate dimension. @@ -1405,16 +1457,21 @@ def contains_branch_seperately(self, other_transform): A common use for this method is to identify if a transform is a blended transform containing an Axes' data transform. e.g.:: - x_isdata, y_isdata = trans.contains_branch_seperately(ax.transData) + x_isdata, y_isdata = trans.contains_branch_separately(ax.transData) """ if self.output_dims != 2: - raise ValueError('contains_branch_seperately only supports ' + raise ValueError('contains_branch_separately only supports ' '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 + # Permanent alias for backwards compatibility (historical typo) + def contains_branch_seperately(self, other_transform): + """:meta private:""" + return self.contains_branch_separately(other_transform) + def __sub__(self, other): """ Compose *self* with the inverse of *other*, cancelling identical terms @@ -1476,14 +1533,14 @@ def transform(self, values): Parameters ---------- values : array-like - The input values as an array of length :attr:`input_dims` or - shape (N, :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 an array of length :attr:`output_dims` or - shape (N, :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). @@ -1521,14 +1578,14 @@ def transform_affine(self, values): Parameters ---------- values : array - The input values as an array of length :attr:`input_dims` or - shape (N, :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 an array of length :attr:`output_dims` or - shape (N, :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) @@ -1546,14 +1603,17 @@ def transform_non_affine(self, values): Parameters ---------- values : array - The input values as an array of length :attr:`input_dims` or - shape (N, :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 an array of length :attr:`output_dims` or - shape (N, :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 @@ -2161,7 +2221,7 @@ def __eq__(self, other): else: return NotImplemented - def contains_branch_seperately(self, transform): + def contains_branch_separately(self, transform): return (self._x.contains_branch(transform), self._y.contains_branch(transform)) @@ -2387,14 +2447,14 @@ 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): + def contains_branch_separately(self, other_transform): # docstring inherited if self.output_dims != 2: - raise ValueError('contains_branch_seperately only supports ' + raise ValueError('contains_branch_separately only supports ' 'transforms with 2 output dimensions') if self == other_transform: return (True, True) - return self._b.contains_branch_seperately(other_transform) + return self._b.contains_branch_separately(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) @@ -2602,27 +2662,6 @@ def get_matrix(self): return self._mtx -@_api.deprecated("3.9") -class BboxTransformToMaxOnly(BboxTransformTo): - """ - `BboxTransformToMaxOnly` 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 @@ -2834,7 +2873,7 @@ def _revalidate(self): super()._revalidate() -def nonsingular(vmin, vmax, expander=0.001, tiny=1e-15, increasing=True): +def _nonsingular(vmin, vmax, expander=0.001, tiny=1e-15, increasing=True): """ Modify the endpoints of a range as needed to avoid singularities. @@ -2892,7 +2931,13 @@ def nonsingular(vmin, vmax, expander=0.001, tiny=1e-15, increasing=True): return vmin, vmax -def interval_contains(interval, val): +@_api.deprecated("3.11") +@_docstring.copy(_nonsingular) +def nonsingular(vmin, vmax, expander=0.001, tiny=1e-15, increasing=True): + return _nonsingular(vmin, vmax, expander, tiny, increasing) + + +def _interval_contains(interval, val): """ Check, inclusively, whether an interval includes a given value. @@ -2914,6 +2959,12 @@ def interval_contains(interval, val): return a <= val <= b +@_api.deprecated("3.11") +@_docstring.copy(_interval_contains) +def interval_contains(interval, val): + return _interval_contains(interval, val) + + def _interval_contains_close(interval, val, rtol=1e-10): """ Check, inclusively, whether an interval includes a given value, with the @@ -2943,7 +2994,7 @@ def _interval_contains_close(interval, val, rtol=1e-10): return a - rtol <= val <= b + rtol -def interval_contains_open(interval, val): +def _interval_contains_open(interval, val): """ Check, excluding endpoints, whether an interval includes a given value. @@ -2963,6 +3014,12 @@ def interval_contains_open(interval, val): return a < val < b or a > val > b +@_api.deprecated("3.11") +@_docstring.copy(_interval_contains_open) +def interval_contains_open(interval, val): + return _interval_contains_open(interval, val) + + def offset_copy(trans, fig=None, x=0.0, y=0.0, units='inches'): """ Return a new transform with an added offset. diff --git a/lib/matplotlib/transforms.pyi b/lib/matplotlib/transforms.pyi index 551487a11c60..ebee3954a3a7 100644 --- a/lib/matplotlib/transforms.pyi +++ b/lib/matplotlib/transforms.pyi @@ -12,7 +12,6 @@ class TransformNode: INVALID_NON_AFFINE: int INVALID_AFFINE: int INVALID: int - is_bbox: bool # Implemented as a standard attr in base class, but functionally readonly and some subclasses implement as such @property def is_affine(self) -> bool: ... @@ -24,7 +23,6 @@ class TransformNode: def frozen(self) -> TransformNode: ... class BboxBase(TransformNode): - is_bbox: bool is_affine: bool def frozen(self) -> Bbox: ... def __array__(self, *args, **kwargs): ... @@ -67,6 +65,7 @@ class BboxBase(TransformNode): @property def extents(self) -> tuple[float, float, float, float]: ... def get_points(self) -> np.ndarray: ... + def _is_finite(self) -> bool: ... def containsx(self, x: float) -> bool: ... def containsy(self, y: float) -> bool: ... def contains(self, x: float, y: float) -> bool: ... @@ -191,9 +190,10 @@ class Transform(TransformNode): @property def depth(self) -> int: ... def contains_branch(self, other: Transform) -> bool: ... - def contains_branch_seperately( + def contains_branch_separately( self, other_transform: Transform ) -> Sequence[bool]: ... + contains_branch_seperately = contains_branch_separately # Alias (historical typo) def __sub__(self, other: Transform) -> Transform: ... def __array__(self, *args, **kwargs) -> np.ndarray: ... def transform(self, values: ArrayLike) -> np.ndarray: ... @@ -254,7 +254,7 @@ class IdentityTransform(Affine2DBase): ... class _BlendedMixin: def __eq__(self, other: object) -> bool: ... - def contains_branch_seperately(self, transform: Transform) -> Sequence[bool]: ... + def contains_branch_separately(self, transform: Transform) -> Sequence[bool]: ... class BlendedGenericTransform(_BlendedMixin, Transform): input_dims: Literal[2] @@ -295,8 +295,6 @@ class BboxTransform(Affine2DBase): class BboxTransformTo(Affine2DBase): def __init__(self, boxout: BboxBase, **kwargs) -> None: ... -class BboxTransformToMaxOnly(BboxTransformTo): ... - class BboxTransformFrom(Affine2DBase): def __init__(self, boxin: BboxBase, **kwargs) -> None: ... @@ -318,6 +316,13 @@ class TransformedPath(TransformNode): 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 nonsingular( vmin: float, vmax: float, @@ -325,7 +330,9 @@ def nonsingular( tiny: float = ..., increasing: bool = ..., ) -> tuple[float, float]: ... +def _interval_contains(interval: tuple[float, float], val: float) -> bool: ... def interval_contains(interval: tuple[float, float], val: float) -> bool: ... +def _interval_contains_open(interval: tuple[float, float], val: float) -> bool: ... def interval_contains_open(interval: tuple[float, float], val: float) -> bool: ... def offset_copy( trans: Transform, diff --git a/lib/matplotlib/tri/_triinterpolate.py b/lib/matplotlib/tri/_triinterpolate.py index 90ad6cf3a76c..2dc62770c7ed 100644 --- a/lib/matplotlib/tri/_triinterpolate.py +++ b/lib/matplotlib/tri/_triinterpolate.py @@ -928,7 +928,7 @@ def get_Kff_and_Ff(self, J, ecc, triangles, Uc): Returns ------- - (Kff_rows, Kff_cols, Kff_vals) Kff matrix in coo format - Duplicate + (Kff_rows, Kff_cols, Kff_vals) Kff matrix in COO format - Duplicate (row, col) entries must be summed. Ff: force vector - dim npts * 3 """ @@ -961,12 +961,12 @@ def get_Kff_and_Ff(self, J, ecc, triangles, Uc): # [ Kcf Kff ] # * As F = K x U one gets straightforwardly: Ff = - Kfc x Uc - # Computing Kff stiffness matrix in sparse coo format + # 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 + # 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] @@ -1178,7 +1178,7 @@ def compute_dz(self): triangles = self._triangles Uc = self.z[self._triangles] - # Building stiffness matrix and force vector in coo format + # 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) @@ -1215,7 +1215,7 @@ def compute_dz(self): class _Sparse_Matrix_coo: def __init__(self, vals, rows, cols, shape): """ - Create a sparse matrix in coo format. + 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 diff --git a/lib/matplotlib/tri/_triinterpolate.pyi b/lib/matplotlib/tri/_triinterpolate.pyi index 8a56b22acdb2..33b2fd8be4cd 100644 --- a/lib/matplotlib/tri/_triinterpolate.pyi +++ b/lib/matplotlib/tri/_triinterpolate.pyi @@ -28,3 +28,5 @@ class CubicTriInterpolator(TriInterpolator): 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 index 1ac6c48a2d7c..5a5b24522d17 100644 --- a/lib/matplotlib/tri/_tripcolor.py +++ b/lib/matplotlib/tri/_tripcolor.py @@ -1,10 +1,11 @@ import numpy as np -from matplotlib import _api +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): """ @@ -54,8 +55,25 @@ def tripcolor(ax, *args, alpha=1.0, norm=None, cmap=None, vmin=None, 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. - other_parameters - All other parameters are the same as for `~.Axes.pcolor`. + %(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) @@ -145,5 +163,7 @@ def tripcolor(ax, *args, alpha=1.0, norm=None, cmap=None, vmin=None, corners = (minx, miny), (maxx, maxy) ax.update_datalim(corners) ax.autoscale_view() - ax.add_collection(collection) + # TODO: check whether the above explicit limit handling can be + # replaced by autolim=True + ax.add_collection(collection, autolim=False) return collection diff --git a/lib/matplotlib/tri/_trirefine.py b/lib/matplotlib/tri/_trirefine.py index 7f5110ab9e21..6a7037ad74fd 100644 --- a/lib/matplotlib/tri/_trirefine.py +++ b/lib/matplotlib/tri/_trirefine.py @@ -64,7 +64,7 @@ def __init__(self, triangulation): def refine_triangulation(self, return_tri_index=False, subdiv=3): """ Compute a uniformly refined triangulation *refi_triangulation* of - the encapsulated :attr:`triangulation`. + 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 diff --git a/lib/matplotlib/typing.py b/lib/matplotlib/typing.py index 20e1022fa0a5..87016984da12 100644 --- a/lib/matplotlib/typing.py +++ b/lib/matplotlib/typing.py @@ -4,14 +4,16 @@ This module contains Type aliases which are useful for Matplotlib and potentially downstream libraries. -.. admonition:: Provisional status of typing +.. 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 typing import Any, Literal, TypeAlias, TypeVar, Union +from collections.abc import Callable from . import path from ._enums import JoinStyle, CapStyle @@ -21,6 +23,8 @@ 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] | @@ -30,27 +34,67 @@ # (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`. +""" -LineStyleType: TypeAlias = str | tuple[float, Sequence[float]] 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 = ( + path.Path | MarkerStyle | str | # str required for "$...$" marker + Literal[ + ".", ",", "o", "v", "^", "<", ">", + "1", "2", "3", "4", "8", "s", "p", + "P", "*", "h", "H", "+", "x", "X", + "D", "d", "|", "_", "none", " ", + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 + ] | list[tuple[int, int]] | tuple[int, Literal[0, 1, 2], int] +) +""" +Marker specification. See :doc:`/gallery/lines_bars_and_markers/marker_reference`. +""" -MarkerType: TypeAlias = str | path.Path | MarkerStyle 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`.""" + +LogLevel: TypeAlias = Literal["NOTSET", "DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] +"""Literal type for valid logging levels accepted by `set_loglevel()`.""" CoordsBaseType = Union[ str, @@ -76,3 +120,458 @@ _HT = TypeVar("_HT", bound=Hashable) HashableList: TypeAlias = list[_HT | "HashableList[_HT]"] """A nested list of Hashable values.""" + +MouseEventType: TypeAlias = Literal[ + "button_press_event", + "button_release_event", + "motion_notify_event", + "scroll_event", + "figure_enter_event", + "figure_leave_event", + "axes_enter_event", + "axes_leave_event", +] + +KeyEventType: TypeAlias = Literal[ + "key_press_event", + "key_release_event" +] + +DrawEventType: TypeAlias = Literal["draw_event"] +PickEventType: TypeAlias = Literal["pick_event"] +ResizeEventType: TypeAlias = Literal["resize_event"] +CloseEventType: TypeAlias = Literal["close_event"] + +EventType: TypeAlias = Literal[ + MouseEventType, + KeyEventType, + DrawEventType, + PickEventType, + ResizeEventType, + CloseEventType, +] + +LegendLocType: TypeAlias = ( + Literal[ + # for simplicity, we don't distinguish the between allowed positions for + # Axes legend and figure legend. It's still better to limit the allowed + # range to the union of both rather than to accept arbitrary strings + "upper right", "upper left", "lower left", "lower right", + "right", "center left", "center right", "lower center", "upper center", + "center", + # Axes only + "best", + # Figure only + "outside upper left", "outside upper center", "outside upper right", + "outside right upper", "outside right center", "outside right lower", + "outside lower right", "outside lower center", "outside lower left", + "outside left lower", "outside left center", "outside left upper", + ] | + tuple[float, float] | + int +) + +RcKeyType: TypeAlias = Literal[ + "agg.path.chunksize", + "animation.bitrate", + "animation.codec", + "animation.convert_args", + "animation.convert_path", + "animation.embed_limit", + "animation.ffmpeg_args", + "animation.ffmpeg_path", + "animation.frame_format", + "animation.html", + "animation.writer", + "axes.autolimit_mode", + "axes.axisbelow", + "axes.edgecolor", + "axes.facecolor", + "axes.formatter.limits", + "axes.formatter.min_exponent", + "axes.formatter.offset_threshold", + "axes.formatter.use_locale", + "axes.formatter.use_mathtext", + "axes.formatter.useoffset", + "axes.grid", + "axes.grid.axis", + "axes.grid.which", + "axes.labelcolor", + "axes.labelpad", + "axes.labelsize", + "axes.labelweight", + "axes.linewidth", + "axes.prop_cycle", + "axes.spines.bottom", + "axes.spines.left", + "axes.spines.right", + "axes.spines.top", + "axes.titlecolor", + "axes.titlelocation", + "axes.titlepad", + "axes.titlesize", + "axes.titleweight", + "axes.titley", + "axes.unicode_minus", + "axes.xmargin", + "axes.ymargin", + "axes.zmargin", + "axes3d.automargin", + "axes3d.depthshade", + "axes3d.depthshade_minalpha", + "axes3d.grid", + "axes3d.mouserotationstyle", + "axes3d.trackballborder", + "axes3d.snap_rotation", + "axes3d.trackballsize", + "axes3d.xaxis.panecolor", + "axes3d.yaxis.panecolor", + "axes3d.zaxis.panecolor", + "backend", + "backend_fallback", + "boxplot.bootstrap", + "boxplot.boxprops.color", + "boxplot.boxprops.linestyle", + "boxplot.boxprops.linewidth", + "boxplot.capprops.color", + "boxplot.capprops.linestyle", + "boxplot.capprops.linewidth", + "boxplot.flierprops.color", + "boxplot.flierprops.linestyle", + "boxplot.flierprops.linewidth", + "boxplot.flierprops.marker", + "boxplot.flierprops.markeredgecolor", + "boxplot.flierprops.markeredgewidth", + "boxplot.flierprops.markerfacecolor", + "boxplot.flierprops.markersize", + "boxplot.meanline", + "boxplot.meanprops.color", + "boxplot.meanprops.linestyle", + "boxplot.meanprops.linewidth", + "boxplot.meanprops.marker", + "boxplot.meanprops.markeredgecolor", + "boxplot.meanprops.markerfacecolor", + "boxplot.meanprops.markersize", + "boxplot.medianprops.color", + "boxplot.medianprops.linestyle", + "boxplot.medianprops.linewidth", + "boxplot.notch", + "boxplot.patchartist", + "boxplot.showbox", + "boxplot.showcaps", + "boxplot.showfliers", + "boxplot.showmeans", + "boxplot.vertical", + "boxplot.whiskerprops.color", + "boxplot.whiskerprops.linestyle", + "boxplot.whiskerprops.linewidth", + "boxplot.whiskers", + "contour.algorithm", + "contour.corner_mask", + "contour.linewidth", + "contour.negative_linestyle", + "date.autoformatter.day", + "date.autoformatter.hour", + "date.autoformatter.microsecond", + "date.autoformatter.minute", + "date.autoformatter.month", + "date.autoformatter.second", + "date.autoformatter.year", + "date.converter", + "date.epoch", + "date.interval_multiples", + "docstring.hardcopy", + "errorbar.capsize", + "errorbar.capthick", + "errorbar.elinewidth", + "figure.autolayout", + "figure.constrained_layout.h_pad", + "figure.constrained_layout.hspace", + "figure.constrained_layout.use", + "figure.constrained_layout.w_pad", + "figure.constrained_layout.wspace", + "figure.dpi", + "figure.edgecolor", + "figure.facecolor", + "figure.figsize", + "figure.frameon", + "figure.hooks", + "figure.labelsize", + "figure.labelweight", + "figure.max_open_warning", + "figure.raise_window", + "figure.subplot.bottom", + "figure.subplot.hspace", + "figure.subplot.left", + "figure.subplot.right", + "figure.subplot.top", + "figure.subplot.wspace", + "figure.titlesize", + "figure.titleweight", + "font.cursive", + "font.enable_last_resort", + "font.family", + "font.fantasy", + "font.monospace", + "font.sans-serif", + "font.serif", + "font.size", + "font.stretch", + "font.style", + "font.variant", + "font.weight", + "grid.alpha", + "grid.color", + "grid.linestyle", + "grid.linewidth", + "grid.major.alpha", + "grid.major.color", + "grid.major.linestyle", + "grid.major.linewidth", + "grid.minor.alpha", + "grid.minor.color", + "grid.minor.linestyle", + "grid.minor.linewidth", + "hatch.color", + "hatch.linewidth", + "hist.bins", + "image.aspect", + "image.cmap", + "image.composite_image", + "image.interpolation", + "image.interpolation_stage", + "image.lut", + "image.origin", + "image.resample", + "interactive", + "keymap.back", + "keymap.copy", + "keymap.forward", + "keymap.fullscreen", + "keymap.grid", + "keymap.grid_minor", + "keymap.help", + "keymap.home", + "keymap.pan", + "keymap.quit", + "keymap.quit_all", + "keymap.save", + "keymap.xscale", + "keymap.yscale", + "keymap.zoom", + "legend.borderaxespad", + "legend.borderpad", + "legend.columnspacing", + "legend.edgecolor", + "legend.facecolor", + "legend.fancybox", + "legend.fontsize", + "legend.framealpha", + "legend.frameon", + "legend.handleheight", + "legend.handlelength", + "legend.handletextpad", + "legend.labelcolor", + "legend.labelspacing", + "legend.linewidth", + "legend.loc", + "legend.markerscale", + "legend.numpoints", + "legend.scatterpoints", + "legend.shadow", + "legend.title_fontsize", + "lines.antialiased", + "lines.color", + "lines.dash_capstyle", + "lines.dash_joinstyle", + "lines.dashdot_pattern", + "lines.dashed_pattern", + "lines.dotted_pattern", + "lines.linestyle", + "lines.linewidth", + "lines.marker", + "lines.markeredgecolor", + "lines.markeredgewidth", + "lines.markerfacecolor", + "lines.markersize", + "lines.scale_dashes", + "lines.solid_capstyle", + "lines.solid_joinstyle", + "macosx.window_mode", + "markers.fillstyle", + "mathtext.bf", + "mathtext.bfit", + "mathtext.cal", + "mathtext.default", + "mathtext.fallback", + "mathtext.fontset", + "mathtext.it", + "mathtext.rm", + "mathtext.sf", + "mathtext.tt", + "patch.antialiased", + "patch.edgecolor", + "patch.facecolor", + "patch.force_edgecolor", + "patch.linewidth", + "path.effects", + "path.simplify", + "path.simplify_threshold", + "path.sketch", + "path.snap", + "pcolor.shading", + "pcolormesh.snap", + "pdf.compression", + "pdf.fonttype", + "pdf.inheritcolor", + "pdf.use14corefonts", + "pgf.preamble", + "pgf.rcfonts", + "pgf.texsystem", + "polaraxes.grid", + "ps.distiller.res", + "ps.fonttype", + "ps.papersize", + "ps.useafm", + "ps.usedistiller", + "savefig.bbox", + "savefig.directory", + "savefig.dpi", + "savefig.edgecolor", + "savefig.facecolor", + "savefig.format", + "savefig.orientation", + "savefig.pad_inches", + "savefig.transparent", + "scatter.edgecolors", + "scatter.marker", + "svg.fonttype", + "svg.hashsalt", + "svg.id", + "svg.image_inline", + "text.antialiased", + "text.color", + "text.hinting", + "text.hinting_factor", + "text.kerning_factor", + "text.latex.preamble", + "text.parse_math", + "text.usetex", + "timezone", + "tk.window_focus", + "toolbar", + "webagg.address", + "webagg.open_in_browser", + "webagg.port", + "webagg.port_retries", + "xaxis.labellocation", + "xtick.alignment", + "xtick.bottom", + "xtick.color", + "xtick.direction", + "xtick.labelbottom", + "xtick.labelcolor", + "xtick.labelsize", + "xtick.labeltop", + "xtick.major.bottom", + "xtick.major.pad", + "xtick.major.size", + "xtick.major.top", + "xtick.major.width", + "xtick.minor.bottom", + "xtick.minor.ndivs", + "xtick.minor.pad", + "xtick.minor.size", + "xtick.minor.top", + "xtick.minor.visible", + "xtick.minor.width", + "xtick.top", + "yaxis.labellocation", + "ytick.alignment", + "ytick.color", + "ytick.direction", + "ytick.labelcolor", + "ytick.labelleft", + "ytick.labelright", + "ytick.labelsize", + "ytick.left", + "ytick.major.left", + "ytick.major.pad", + "ytick.major.right", + "ytick.major.size", + "ytick.major.width", + "ytick.minor.left", + "ytick.minor.ndivs", + "ytick.minor.pad", + "ytick.minor.right", + "ytick.minor.size", + "ytick.minor.visible", + "ytick.minor.width", + "ytick.right", +] + +RcGroupKeyType: TypeAlias = Literal[ + "agg", + "agg.path", + "animation", + "axes", + "axes.formatter", + "axes.grid", + "axes.spines", + "axes3d", + "axes3d.xaxis", + "axes3d.yaxis", + "axes3d.zaxis", + "boxplot", + "boxplot.boxprops", + "boxplot.capprops", + "boxplot.flierprops", + "boxplot.meanprops", + "boxplot.medianprops", + "boxplot.whiskerprops", + "contour", + "date", + "date.autoformatter", + "docstring", + "errorbar", + "figure", + "figure.constrained_layout", + "figure.subplot", + "font", + "grid", + "grid.major", + "grid.minor", + "hatch", + "hist", + "image", + "keymap", + "legend", + "lines", + "macosx", + "markers", + "mathtext", + "patch", + "path", + "pcolor", + "pcolormesh", + "pdf", + "pgf", + "polaraxes", + "ps", + "ps.distiller", + "savefig", + "scatter", + "svg", + "text", + "text.latex", + "tk", + "webagg", + "xaxis", + "xtick", + "xtick.major", + "xtick.minor", + "yaxis", + "ytick", + "ytick.major", + "ytick.minor", +] diff --git a/lib/matplotlib/widgets.py b/lib/matplotlib/widgets.py index 9c676574310c..92c9b779ff09 100644 --- a/lib/matplotlib/widgets.py +++ b/lib/matplotlib/widgets.py @@ -1,8 +1,6 @@ """ -GUI neutral widgets -=================== - Widgets that are designed to work for any of the GUI backends. + 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 @@ -11,7 +9,10 @@ from contextlib import ExitStack import copy +import enum +import functools import itertools +import weakref from numbers import Integral, Number from cycler import cycler @@ -116,8 +117,11 @@ class AxesWidget(Widget): def __init__(self, ax): self.ax = ax self._cids = [] + self._blit_background_id = None - canvas = property(lambda self: self.ax.get_figure(root=True).canvas) + canvas = property( + lambda self: getattr(self.ax.get_figure(root=True), 'canvas', None) + ) def connect_event(self, event, callback): """ @@ -134,15 +138,61 @@ 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))) + def ignore(self, event): + # docstring inherited + return super().ignore(event) or self.canvas is None + + def _set_cursor(self, cursor): + """Update the canvas cursor.""" + self.ax.get_figure(root=True).canvas.set_cursor(cursor) + + def _save_blit_background(self, background): + """ + Save a blit background. + + The background is stored on the canvas in a uniquely identifiable way. + It should be read back via `._load_blit_background`. Be prepared that + some events may invalidate the background, in which case + `._load_blit_background` will return None. + + This currently allows at most one background per widget, which is + good enough for all existing widgets. + """ + if self._blit_background_id is None: + bbid = self.canvas._get_blit_background_id() + weakref.finalize(self, self.canvas._release_blit_background_id, bbid) + self._blit_background_id = bbid + self.canvas._blit_backgrounds[self._blit_background_id] = background + + def _load_blit_background(self): + """Load a blit background; may be None at any time.""" + return self.canvas._blit_backgrounds.get(self._blit_background_id) + + +def _call_with_reparented_event(func): + """ + Event callback decorator ensuring that the callback is called with an event + that has been reparented to the widget's axes. + """ + # This decorator handles the possibility that event.inaxes != self.ax + # (e.g. 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. + @functools.wraps(func) + def wrapper(self, event): + if event.inaxes is not self.ax: + event = copy.copy(event) + event.guiEvent = None + event.inaxes = self.ax + try: + event.xdata, event.ydata = ( + self.ax.transData.inverted().transform((event.x, event.y))) + except ValueError: # cf LocationEvent._set_inaxes. + event.xdata = event.ydata = None + return func(self, event) + + return wrapper class Button(AxesWidget): @@ -195,7 +245,7 @@ def __init__(self, ax, label, image=None, horizontalalignment='center', transform=ax.transAxes) - self._useblit = useblit and self.canvas.supports_blit + self._useblit = useblit self._observers = cbook.CallbackRegistry(signals=["clicked"]) @@ -209,12 +259,14 @@ def __init__(self, ax, label, image=None, self.color = color self.hovercolor = hovercolor + @_call_with_reparented_event def _click(self, event): 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) + @_call_with_reparented_event def _release(self, event): if self.ignore(event) or event.canvas.mouse_grabber != self.ax: return @@ -222,6 +274,7 @@ def _release(self, event): if self.eventson and self.ax.contains(event)[0]: self._observers.process('clicked', event) + @_call_with_reparented_event def _motion(self, event): if self.ignore(event): return @@ -229,7 +282,7 @@ def _motion(self, event): if not colors.same_color(c, self.ax.get_facecolor()): self.ax.set_facecolor(c) if self.drawon: - if self._useblit: + if self._useblit and self.canvas.supports_blit: self.ax.draw_artist(self.ax) self.canvas.blit(self.ax.bbox) else: @@ -273,10 +326,10 @@ def __init__(self, ax, orientation, closedmin, closedmax, self.valfmt = valfmt if orientation == "vertical": - ax.set_ylim((valmin, valmax)) + ax.set_ylim(valmin, valmax) axis = ax.yaxis else: - ax.set_xlim((valmin, valmax)) + ax.set_xlim(valmin, valmax) axis = ax.xaxis self._fmt = axis.get_major_formatter() @@ -364,8 +417,9 @@ def __init__(self, ax, label, valmin, valmax, *, valinit=0.5, valfmt=None, The slider initial position. valfmt : str, default: None - %-format string used to format the slider value. If None, a - `.ScalarFormatter` is used instead. + The way to format the slider value. If a string, it must be in %-format. + If a callable, it must have the signature ``valfmt(val: float) -> str``. + If None, a `.ScalarFormatter` is used. closedmin : bool, default: True Whether the slider interval is closed on the bottom. @@ -520,6 +574,7 @@ def _value_in_bounds(self, val): val = self.slidermax.val return val + @_call_with_reparented_event def _update(self, event): """Update the slider position.""" if self.ignore(event) or event.button != 1: @@ -538,16 +593,18 @@ def _update(self, event): event.canvas.release_mouse(self.ax) return - xdata, ydata = self._get_data_coords(event) val = self._value_in_bounds( - xdata if self.orientation == 'horizontal' else ydata) + event.xdata if self.orientation == 'horizontal' else event.ydata) if val not in [None, self.val]: self.set_val(val) def _format(self, val): """Pretty-print *val*.""" if self.valfmt is not None: - return self.valfmt % val + if callable(self.valfmt): + return self.valfmt(val) + else: + return self.valfmt % val else: _, s, _ = self._fmt.format_ticks([self.valmin, val, self.valmax]) # fmt.get_offset is actually the multiplicative factor, if any. @@ -644,9 +701,11 @@ def __init__( 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. + valfmt : str or callable, default: None + The way to format the range's minimal and maximal values. If a + string, it must be in %-format. If a callable, it must have the + signature ``valfmt(val: float) -> str``. If None, a + `.ScalarFormatter` is used. closedmin : bool, default: True Whether the slider interval is closed on the bottom. @@ -853,6 +912,7 @@ def _update_val_from_pos(self, pos): else: self._active_handle.set_xdata([val]) + @_call_with_reparented_event def _update(self, event): """Update the slider position.""" if self.ignore(event) or event.button != 1: @@ -873,11 +933,10 @@ def _update(self, event): 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] + [h.get_xdata()[0] - event.xdata for h in self._handles] if self.orientation == "horizontal" else - [h.get_ydata()[0] - ydata for h in self._handles])) + [h.get_ydata()[0] - event.ydata for h in self._handles])) handle = self._handles[handle_index] # these checks ensure smooth behavior if the handles swap which one @@ -885,12 +944,16 @@ def _update(self, event): if handle is not self._active_handle: self._active_handle = handle - self._update_val_from_pos(xdata if self.orientation == "horizontal" else ydata) + self._update_val_from_pos( + event.xdata if self.orientation == "horizontal" else event.ydata) def _format(self, val): """Pretty-print *val*.""" if self.valfmt is not None: - return f"({self.valfmt % val[0]}, {self.valfmt % val[1]})" + if callable(self.valfmt): + return f"({self.valfmt(val[0])}, {self.valfmt(val[1])})" + else: + return f"({self.valfmt % val[0]}, {self.valfmt % val[1]})" else: _, s1, s2, _ = self._fmt.format_ticks( [self.valmin, *val, self.valmax] @@ -973,14 +1036,208 @@ def _expand_text_props(props): return cycler(**props)() if props else itertools.repeat({}) -class CheckButtons(AxesWidget): +class _Buttons(AxesWidget): + """ + The base class for `CheckButtons` and `RadioButtons`. + + This class provides common functionality for button widgets, + such as handling click events, managing button labels, and connecting callbacks. + + The class itself is private and may be changed or removed without prior warning. + However, the public API it provides to subclasses is stable and considered + public on the subclasses. + """ + + def __init__(self, ax, labels, *, useblit=True, label_props=None, layout=None, + **kwargs): + super().__init__(ax) + + ax.set_xticks([]) + ax.set_yticks([]) + ax.set_navigate(False) + + self._useblit = useblit + + self._init_layout(layout, labels, label_props) + text_size = np.array([text.get_fontsize() for text in self.labels]) / 2 + + self._init_props(text_size, **kwargs) + + 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 _init_layout(self, layout, labels, label_props): + + label_props = _expand_text_props(label_props) + + if layout is None: + # legacy hard-coded vertical layout + self._buttons_xs = [0.15] * len(labels) + self._buttons_ys = np.linspace(1, 0, len(labels)+2)[1:-1] + self.labels = [ + self.ax.text(0.25, y, label, transform=self.ax.transAxes, + horizontalalignment="left", verticalalignment="center", + **props) + for y, label, props in zip(self._buttons_ys, labels, label_props)] + return + + # New layout algorithm with text measurement + # Parse layout parameter + n_labels = len(labels) + match layout: + case "vertical": + n_rows, n_cols = n_labels, 1 + case "horizontal": + n_rows, n_cols = 1, n_labels + case (int() as n_rows, int() as n_cols): + if n_rows * n_cols < n_labels: + raise ValueError( + f"layout {layout} has {n_rows * n_cols} positions but " + f"{n_labels} labels were provided" + ) + case _: + raise ValueError( + "layout must be None, 'vertical', 'horizontal', or a (rows, cols) " + f"tuple; got {layout!r}") + + # Define spacing in points for DPI-independent sizing + fig = self.ax.get_figure(root=False) + axes_width_display = 72 * self.ax.bbox.transformed( + fig.dpi_scale_trans.inverted() + ).width + left_margin_display = 11 # points + button_text_offset_display = 5.5 # points + col_spacing_display = 11 # points + + # Convert to axes coordinates + left_margin = left_margin_display / axes_width_display + button_text_offset = button_text_offset_display / axes_width_display + col_spacing = col_spacing_display / axes_width_display + + # Create text objects to measure widths. + # We create Text objects directly rather than using ax.text() since we're + # only measuring them and only later add them to the axes. + self.labels = [ + mtext.Text(0, 0, text=label, transform=self.ax.transAxes, + horizontalalignment="left", verticalalignment="center", + **props) + for label, props in zip(labels, label_props) + ] + # Set figure reference so Text objects can access figure properties + for text in self.labels: + text.set_figure(fig) + # Calculate max text width per column (in axes coordinates) + renderer = self.ax.figure.canvas.get_renderer() + inv_trans = fig.dpi_scale_trans.inverted() + col_widths = [ + max( + ( + text.get_window_extent(renderer).transformed(inv_trans).width * 72 + for text in self.labels[col_idx::n_cols] + ), + default=0, + ) + / axes_width_display + for col_idx in range(n_cols) + ] + + # Center rows vertically in the axes + ys_per_row = np.linspace(1, 0, n_rows + 2)[1:-1] + # Calculate x positions based on text widths + col_x_positions = [left_margin] # First column starts at left margin + for col_idx in range(n_cols - 1): + col_x_positions.append( + col_x_positions[-1] + + button_text_offset + + col_widths[col_idx] + + col_spacing + ) + label_idx = np.arange(n_labels) + self._buttons_xs = np.take(col_x_positions, label_idx % n_cols) + self._buttons_ys = ys_per_row[label_idx // n_cols] + for text, x, y in zip(self.labels, self._buttons_xs, self._buttons_ys): + text.set_position((x + button_text_offset, y)) + self.ax.add_artist(text) + + def _init_props(self, text_size, **kwargs): + raise NotImplementedError("This method should be defined in subclasses") + + def _clear(self, event): + """Internal event handler to clear the buttons.""" + if self.ignore(event) or self.canvas.is_saving(): + return + if self._useblit and self.canvas.supports_blit: + self._save_blit_background(self.canvas.copy_from_bbox(self.ax.bbox)) + self.ax.draw_artist(self._buttons) + + def set_label_props(self, props): + """ + Set properties of the `.Text` labels. + + .. versionadded:: 3.7 + + Parameters + ---------- + props : dict + Dictionary of `.Text` properties to be used for the labels. Same + format as label_props argument of :class:`RadioButtons` or + :class:`CheckButtons`. + """ + _api.check_isinstance(dict, props=props) + props = _expand_text_props(props) + for text, prop in zip(self.labels, props): + text.update(prop) + + @_call_with_reparented_event + 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._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 on_clicked(self, func): + """ + Connect the callback function *func* to button click events. + + 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) + + def disconnect(self, cid): + """Remove the observer with connection id *cid*.""" + self._observers.disconnect(cid) + + +class CheckButtons(_Buttons): r""" A GUI neutral set of check buttons. For the check buttons to remain responsive you must keep a reference to this object. - Connect to the CheckButtons with the `.on_clicked` method. + Connect to the CheckButtons with the `~._Buttons.on_clicked` method. Attributes ---------- @@ -990,7 +1247,7 @@ class CheckButtons(AxesWidget): The text label objects of the check buttons. """ - def __init__(self, ax, labels, actives=None, *, useblit=True, + def __init__(self, ax, labels, actives=None, *, layout=None, useblit=True, label_props=None, frame_props=None, check_props=None): """ Add check buttons to `~.axes.Axes` instance *ax*. @@ -1004,14 +1261,41 @@ def __init__(self, ax, labels, actives=None, *, useblit=True, 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. + layout : None or "vertical" or "horizontal" or (int, int), default: None + The layout of the check buttons. Options are: + + - ``None``: Use legacy vertical layout (default). + - ``"vertical"``: Arrange buttons in a single column with + dynamic positioning based on text widths. + - ``"horizontal"``: Arrange buttons in a single row with + dynamic positioning based on text widths. + - ``(rows, cols)`` tuple: Arrange buttons in a grid with the + specified number of rows and columns. Buttons are placed + left-to-right, top-to-bottom with dynamic positioning. + + The layout options "vertical", "horizontal" and ``(rows, cols)`` + create ``mtext.Text`` objects to determine exact text sizes, and + then they are added to the Axes. This is usually okay, but may cause + side-effects and has a slight performance impact. Therefore the + default ``None`` value avoids this. + + .. admonition:: Provisional + The new layout options are provisional. Their algorithmic + behavior, including the exact positions of buttons and labels, + may still change without prior warning. + + .. versionadded:: 3.11 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. + label_props : dict of lists, optional + Dictionary of `.Text` properties to be used for the labels. Each + dictionary value should be a list of at least a single element. If + the list is of length M, its values are cycled such that the Nth + label gets the (N mod M) property. .. versionadded:: 3.7 frame_props : dict, optional @@ -1027,97 +1311,52 @@ def __init__(self, ax, labels, actives=None, *, useblit=True, .. 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) - - if actives is None: - actives = [False] * len(labels) - - 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 + super().__init__(ax, labels, layout=layout, useblit=useblit, + label_props=label_props, actives=actives, + frame_props=frame_props, check_props=check_props) + def _init_props(self, text_size, actives, frame_props, check_props): frame_props = { 's': text_size**2, 'linewidth': 1, **cbook.normalize_kwargs(frame_props, collections.PathCollection), 'marker': 's', - 'transform': ax.transAxes, + 'transform': self.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) + self._frames = self.ax.scatter( + self._buttons_xs, + self._buttons_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, + 'transform': self.ax.transAxes, + 'animated': self._useblit and self.canvas.supports_blit, + # TODO: This may need an update when switching out the canvas. + # Can set this to `_useblit` only and live with the animated=True + # overhead on unsupported backends. } check_props.setdefault('facecolor', check_props.pop('color', 'black')) - self._checks = ax.scatter([0.15] * len(ys), ys, **check_props) + self._buttons = self.ax.scatter( + self._buttons_xs, + self._buttons_ys, + **check_props + ) + if actives is None: + actives = [False] * len(self.labels) # 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.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._checks) - - 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. - - .. versionadded:: 3.7 - - 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) - def set_frame_props(self, props): """ Set properties of the check button frames. @@ -1151,7 +1390,7 @@ def set_check_props(self, 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) + self._buttons.update(props) # If new colours are supplied, then we must re-apply the status. self._init_status(actives) @@ -1159,7 +1398,7 @@ 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 ---------- @@ -1183,17 +1422,18 @@ def set_active(self, index, state=None): invisible = colors.to_rgba('none') - facecolors = self._checks.get_facecolor() + facecolors = self._buttons.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) + self._buttons.set_facecolor(facecolors) if self.drawon: - if self._useblit: - if self._background is not None: - self.canvas.restore_region(self._background) - self.ax.draw_artist(self._checks) + if self._useblit and self.canvas.supports_blit: + background = self._load_blit_background() + if background is not None: + self.canvas.restore_region(background) + self.ax.draw_artist(self._buttons) self.canvas.blit(self.ax.bbox) else: self.canvas.draw() @@ -1209,18 +1449,18 @@ def _init_status(self, actives): 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() + self._active_check_colors = self._buttons.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( + self._buttons.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)) + self._buttons.set_facecolor(['none'] * len(self._active_check_colors)) if hasattr(self, '_lines'): for l1, l2 in self._lines: @@ -1239,7 +1479,7 @@ def get_status(self): Return a list of the status (True/False) of all of the check buttons. """ return [not colors.same_color(color, colors.to_rgba("none")) - for color in self._checks.get_facecolors()] + for color in self._buttons.get_facecolors()] def get_checked_labels(self): """Return a list of labels currently checked by user.""" @@ -1248,31 +1488,6 @@ def get_checked_labels(self): zip(self.labels, self.get_status()) if box_checked] - def on_clicked(self, func): - """ - Connect the callback function *func* to button click events. - - 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', lambda text: func(text)) - - def disconnect(self, cid): - """Remove the observer with connection id *cid*.""" - self._observers.disconnect(cid) - class TextBox(AxesWidget): """ @@ -1320,7 +1535,7 @@ def __init__(self, ax, label, initial='', *, """ super().__init__(ax) - self._text_position = _api.check_getitem( + self._text_position = _api.getitem_checked( {"left": 0.05, "center": 0.5, "right": 0.95}, textalignment=textalignment) @@ -1396,6 +1611,7 @@ def _rendercursor(self): fig.canvas.draw() + @_call_with_reparented_event def _release(self, event): if self.ignore(event): return @@ -1403,6 +1619,7 @@ def _release(self, event): return event.canvas.release_mouse(self.ax) + @_call_with_reparented_event def _keypress(self, event): if self.ignore(event): return @@ -1485,6 +1702,7 @@ def stop_typing(self): # call it once we've already done our cleanup. self._observers.process('submit', self.text) + @_call_with_reparented_event def _click(self, event): if self.ignore(event): return @@ -1500,9 +1718,11 @@ def _click(self, event): self.cursor_index = self.text_disp._char_index_at(event.x) self._rendercursor() + @_call_with_reparented_event def _resize(self, event): self.stop_typing() + @_call_with_reparented_event def _motion(self, event): if self.ignore(event): return @@ -1534,14 +1754,14 @@ def disconnect(self, cid): self._observers.disconnect(cid) -class RadioButtons(AxesWidget): +class RadioButtons(_Buttons): """ A GUI neutral radio button. For the buttons to remain responsive you must keep a reference to this object. - Connect to the RadioButtons with the `.on_clicked` method. + Connect to the RadioButtons with the `~._Buttons.on_clicked` method. Attributes ---------- @@ -1557,7 +1777,7 @@ class RadioButtons(AxesWidget): The index of the selected button. """ - def __init__(self, ax, labels, active=0, activecolor=None, *, + def __init__(self, ax, labels, active=0, activecolor=None, *, layout=None, useblit=True, label_props=None, radio_props=None): """ Add radio buttons to an `~.axes.Axes`. @@ -1573,14 +1793,41 @@ def __init__(self, ax, labels, active=0, activecolor=None, *, activecolor : :mpltype:`color` The color of the selected button. The default is ``'blue'`` if not specified here or in *radio_props*. + layout : None or "vertical" or "horizontal" or (int, int), default: None + The layout of the radio buttons. Options are: + + - ``None``: Use legacy vertical layout (default). + - ``"vertical"``: Arrange buttons in a single column with + dynamic positioning based on text widths. + - ``"horizontal"``: Arrange buttons in a single row with + dynamic positioning based on text widths. + - ``(rows, cols)`` tuple: Arrange buttons in a grid with the + specified number of rows and columns. Buttons are placed + left-to-right, top-to-bottom with dynamic positioning. + + The layout options "vertical", "horizontal" and ``(rows, cols)`` + create ``mtext.Text`` objects to determine exact text sizes, and + then they are added to the Axes. This is usually okay, but may cause + side-effects and has a slight performance impact. Therefore the + default ``None`` value avoids this. + + .. admonition:: Provisional + The new layout options are provisional. Their algorithmic + behavior, including the exact positions of buttons and labels, + may still change without prior warning. + + .. versionadded:: 3.11 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. + label_props : dict of lists, optional + Dictionary of `.Text` properties to be used for the labels. Each + dictionary value should be a list of at least a single element. If + the list is of length M, its values are cycled such that the Nth + label gets the (N mod M) property. .. versionadded:: 3.7 radio_props : dict, optional @@ -1595,8 +1842,6 @@ def __init__(self, ax, labels, active=0, activecolor=None, *, .. versionadded:: 3.7 """ - super().__init__(ax) - _api.check_isinstance((dict, None), label_props=label_props, radio_props=radio_props) @@ -1610,92 +1855,46 @@ def __init__(self, ax, labels, active=0, activecolor=None, *, '*activecolor* will be ignored.') else: activecolor = 'blue' # Default. + super().__init__(ax, labels, useblit=useblit, label_props=label_props, + active=active, layout=layout, activecolor=activecolor, + radio_props=radio_props) 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) - - 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 - + def _init_props(self, text_size, active, activecolor, radio_props): radio_props = { 's': text_size**2, **radio_props, 'marker': 'o', - 'transform': ax.transAxes, - 'animated': self._useblit, + 'transform': self.ax.transAxes, + 'animated': self._useblit and self.canvas.supports_blit, + # TODO: This may need an update when switching out the canvas. + # Can set this to `_useblit` only and live with the animated=True + # overhead on unsupported backends. + } 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) + self._buttons = self.ax.scatter( + self._buttons_xs, + self._buttons_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), + self._active_colors = np.repeat(self._active_colors, len(self.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 - - 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) - def set_radio_props(self, props): """ Set properties of the `.Text` labels. @@ -1734,7 +1933,7 @@ 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 ---------- @@ -1756,9 +1955,10 @@ def set_active(self, index): self._buttons.set_facecolor(button_facecolors) if self.drawon: - if self._useblit: - if self._background is not None: - self.canvas.restore_region(self._background) + if self._useblit and self.canvas.supports_blit: + background = self._load_blit_background() + if background is not None: + self.canvas.restore_region(background) self.ax.draw_artist(self._buttons) self.canvas.blit(self.ax.bbox) else: @@ -1771,31 +1971,6 @@ 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. - - 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) - - def disconnect(self, cid): - """Remove the observer with connection id *cid*.""" - self._observers.disconnect(cid) - class SubplotTool(Widget): """ @@ -1841,7 +2016,7 @@ def __init__(self, targetfig, toolfig): self.sliderbottom.slidermax = self.slidertop self.slidertop.slidermin = self.sliderbottom - bax = toolfig.add_axes([0.8, 0.05, 0.15, 0.075]) + bax = toolfig.add_axes((0.8, 0.05, 0.15, 0.075)) self.buttonreset = Button(bax, 'Reset') self.buttonreset.on_clicked(self._on_reset) @@ -1907,14 +2082,13 @@ def __init__(self, ax, *, horizOn=True, vertOn=True, useblit=False, self.visible = True self.horizOn = horizOn self.vertOn = vertOn - self.useblit = useblit and self.canvas.supports_blit + self.useblit = useblit and self.canvas.supports_blit # TODO: make dynamic if self.useblit: lineprops['animated'] = True self.lineh = ax.axhline(ax.get_ybound()[0], visible=False, **lineprops) self.linev = ax.axvline(ax.get_xbound()[0], visible=False, **lineprops) - self.background = None self.needclear = False def clear(self, event): @@ -1922,8 +2096,9 @@ def clear(self, 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._save_blit_background(self.canvas.copy_from_bbox(self.ax.bbox)) + @_call_with_reparented_event def onmove(self, event): """Internal event handler to draw the cursor when the mouse moves.""" if self.ignore(event): @@ -1938,17 +2113,17 @@ def onmove(self, event): self.needclear = False return self.needclear = True - xdata, ydata = self._get_data_coords(event) - self.linev.set_xdata((xdata, xdata)) + self.linev.set_xdata((event.xdata, event.xdata)) self.linev.set_visible(self.visible and self.vertOn) - self.lineh.set_ydata((ydata, ydata)) + self.lineh.set_ydata((event.ydata, event.ydata)) self.lineh.set_visible(self.visible and self.horizOn) 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) + background = self._load_blit_background() + if background is not None: + self.canvas.restore_region(background) self.ax.draw_artist(self.linev) self.ax.draw_artist(self.lineh) self.canvas.blit(self.ax.bbox) @@ -1961,12 +2136,19 @@ class MultiCursor(Widget): Provide a vertical (default) and/or horizontal line cursor shared between multiple Axes. + Call signatures:: + + MultiCursor(axes, *, ...) + MultiCursor(canvas, axes, *, ...) # deprecated + For the cursor to remain responsive you must keep a reference to it. Parameters ---------- canvas : object - This parameter is entirely unused and only kept for back-compatibility. + This parameter is entirely unused. + + .. deprecated:: 3.11 axes : list of `~matplotlib.axes.Axes` The `~.axes.Axes` to attach the cursor to. @@ -1993,11 +2175,25 @@ class MultiCursor(Widget): See :doc:`/gallery/widgets/multicursor`. """ - def __init__(self, canvas, axes, *, useblit=True, horizOn=False, vertOn=True, + def __init__(self, *args, 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 + # Deprecation of canvas as the first attribute. When the deprecation expires: + # - change the signature to __init__(self, axes, *, ...) + # - delete the "Call signatures" block in the docstring + # - delete this block + kwargs = {k: lineprops.pop(k) + for k in list(lineprops) if k in ("canvas", "axes")} + params = _api.select_matching_signature( + [lambda axes: locals(), lambda canvas, axes: locals()], *args, **kwargs) + if "canvas" in params: + _api.warn_deprecated( + "3.11", + message="The canvas parameter in MultiCursor is unused and deprecated " + "since %(since)s. Please remove it and call MultiCursor(axes) " + "instead of MultiCursor(canvas, axes). The latter will start raising " + "an error in %(removal)s" + ) + axes = params["axes"] self.axes = axes self.horizOn = horizOn @@ -2016,6 +2212,7 @@ def __init__(self, canvas, axes, *, useblit=True, horizOn=False, vertOn=True, self.useblit = ( useblit and all(canvas.supports_blit for canvas in self._canvas_infos)) + # TODO: make dynamic if self.useblit: lineprops['animated'] = True @@ -2090,6 +2287,16 @@ def onmove(self, event): class _SelectorWidget(AxesWidget): + """ + The base class for selector widgets. + + This class provides common functionality for selector widgets, + such as handling mouse and keyboard events, managing state modifier keys, etc. + + The class itself is private and may be changed or removed without prior warning. + However, the public API it provides to subclasses is stable and considered + public on the subclasses. + """ def __init__(self, ax, onselect=None, useblit=False, button=None, state_modifier_keys=None, use_data_coordinates=False): @@ -2100,7 +2307,7 @@ def __init__(self, ax, onselect=None, useblit=False, button=None, self.onselect = lambda *args: None else: self.onselect = onselect - self.useblit = useblit and self.canvas.supports_blit + self._useblit = useblit self.connect_default_events() self._state_modifier_keys = dict(move=' ', clear='escape', @@ -2109,8 +2316,6 @@ def __init__(self, ax, onselect=None, useblit=False, button=None, self._state_modifier_keys.update(state_modifier_keys or {}) self._use_data_coordinates = use_data_coordinates - self.background = None - if isinstance(button, Integral): self.validButtons = [button] else: @@ -2126,6 +2331,11 @@ def __init__(self, ax, onselect=None, useblit=False, button=None, self._prev_event = None self._state = set() + @property + def useblit(self): + """Return whether blitting is used (requested and supported by canvas).""" + return self._useblit and self.canvas.supports_blit + def set_active(self, active): super().set_active(active) if active: @@ -2149,6 +2359,8 @@ def update_background(self, event): # `release` can call a draw event even when `ignore` is True. if not self.useblit: return + if self.canvas.is_saving(): + return # saving does not use blitting # 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). @@ -2164,7 +2376,7 @@ def update_background(self, event): 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) + self._save_blit_background(self.canvas.copy_from_bbox(self.ax.bbox)) if needs_redraw: for artist in artists: self.ax.draw_artist(artist) @@ -2181,7 +2393,9 @@ def connect_default_events(self): def ignore(self, event): # docstring inherited - if not self.active or not self.ax.get_visible(): + if super().ignore(event): + return True + if not self.ax.get_visible(): return True # If canvas was locked if not self.canvas.widgetlock.available(self): @@ -2209,8 +2423,9 @@ def update(self): 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) + background = self._load_blit_background() + if background is not None: + self.canvas.restore_region(background) else: self.update_background(None) # We need to draw all artists, which are not included in the @@ -2228,9 +2443,8 @@ def _get_data(self, event): """Get the xdata and ydata for event, with limits.""" if event.xdata is None: return None, None - xdata, ydata = self._get_data_coords(event) - xdata = np.clip(xdata, *self.ax.get_xbound()) - ydata = np.clip(ydata, *self.ax.get_ybound()) + xdata = np.clip(event.xdata, *self.ax.get_xbound()) + ydata = np.clip(event.ydata, *self.ax.get_ybound()) return xdata, ydata def _clean_event(self, event): @@ -2250,6 +2464,7 @@ def _clean_event(self, event): self._prev_event = event return event + @_call_with_reparented_event def press(self, event): """Button press handler and validator.""" if not self.ignore(event): @@ -2268,6 +2483,7 @@ def press(self, event): def _press(self, event): """Button press event handler.""" + @_call_with_reparented_event def release(self, event): """Button release event handler and validator.""" if not self.ignore(event) and self._eventpress: @@ -2283,6 +2499,7 @@ def release(self, event): def _release(self, event): """Button release event handler.""" + @_call_with_reparented_event def onmove(self, event): """Cursor move event handler and validator.""" if not self.ignore(event) and self._eventpress: @@ -2294,6 +2511,7 @@ def onmove(self, event): def _onmove(self, event): """Cursor move event handler.""" + @_call_with_reparented_event def on_scroll(self, event): """Mouse scroll event handler and validator.""" if not self.ignore(event): @@ -2302,6 +2520,7 @@ def on_scroll(self, event): def _on_scroll(self, event): """Mouse scroll event handler.""" + @_call_with_reparented_event def on_key_press(self, event): """Key press event handler and validator for all selection widgets.""" if self.active: @@ -2326,6 +2545,7 @@ def on_key_press(self, event): def _on_key_press(self, event): """Key press event handler - for widget-specific key press actions.""" + @_call_with_reparented_event def on_key_release(self, event): """Key release event handler and validator.""" if self.active: @@ -2381,7 +2601,7 @@ def set_props(self, **props): 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 + *handle_props* argument in the selector docstring to know which properties are supported. """ if not hasattr(self, '_handles_artists'): @@ -2405,13 +2625,15 @@ def _validate_state(self, state): def add_state(self, state): """ Add a state to define the widget's behavior. See the - `state_modifier_keys` parameters for details. + *state_modifier_keys* parameter in the constructor of the concrete + selector class for details. Parameters ---------- state : str Must be a supported state of the selector. See the - `state_modifier_keys` parameters for details. + *state_modifier_keys* parameter in the constructor of the concrete + selector class for details. Raises ------ @@ -2425,13 +2647,15 @@ def add_state(self, state): def remove_state(self, state): """ Remove a state to define the widget's behavior. See the - `state_modifier_keys` parameters for details. + *state_modifier_keys* parameter in the constructor of the concrete + selector class for details. Parameters ---------- state : str Must be a supported state of the selector. See the - `state_modifier_keys` parameters for details. + *state_modifier_keys* parameter in the constructor of the concrete + selector class for details. Raises ------ @@ -2543,7 +2767,14 @@ def __init__(self, ax, onselect, direction, *, minspan=0, useblit=False, if props is None: props = dict(facecolor='red', alpha=0.5) - props['animated'] = self.useblit + # Note: We set this based on the user setting during ínitialization, + # not on the actual capability of blitting. But the value is + # irrelevant if the backend does not support blitting, so that + # we don't have to dynamically update this on the backend. + # This relies on the current behavior that the request for + # useblit is fixed during initialization and cannot be changed + # afterwards. + props['animated'] = self._useblit self.direction = direction self._extents_on_press = None @@ -2609,7 +2840,7 @@ def _setup_edge_handles(self, props): self._edge_handles = ToolLineHandles(self.ax, positions, direction=self.direction, line_props=props, - useblit=self.useblit) + useblit=self._useblit) @property def _handles_artists(self): @@ -2618,7 +2849,7 @@ def _handles_artists(self): else: return () - def _set_cursor(self, enabled): + def _set_span_cursor(self, *, enabled): """Update the canvas cursor based on direction of the selector.""" if enabled: cursor = (backend_tools.Cursors.RESIZE_HORIZONTAL @@ -2627,7 +2858,7 @@ def _set_cursor(self, enabled): else: cursor = backend_tools.Cursors.POINTER - self.ax.get_figure(root=True).canvas.set_cursor(cursor) + self._set_cursor(cursor) def connect_default_events(self): # docstring inherited @@ -2637,7 +2868,7 @@ def connect_default_events(self): def _press(self, event): """Button press event handler.""" - self._set_cursor(True) + self._set_span_cursor(enabled=True) if self._interactive and self._selection_artist.get_visible(): self._set_active_handle(event) else: @@ -2647,8 +2878,7 @@ def _press(self, event): # Clear previous rectangle before drawing new rectangle. self.update() - xdata, ydata = self._get_data_coords(event) - v = xdata if self.direction == 'horizontal' else ydata + v = event.xdata if self.direction == 'horizontal' else event.ydata if self._active_handle is None and not self.ignore_event_outside: # when the press event outside the span, we initially set the @@ -2685,9 +2915,10 @@ def direction(self, direction): else: self._direction = direction + @_call_with_reparented_event def _release(self, event): """Button release event handler.""" - self._set_cursor(False) + self._set_span_cursor(enabled=False) if not self._interactive: self._selection_artist.set_visible(False) @@ -2716,6 +2947,7 @@ def _release(self, event): return False + @_call_with_reparented_event def _hover(self, event): """Update the canvas cursor if it's over a handle.""" if self.ignore(event): @@ -2729,17 +2961,16 @@ def _hover(self, event): return _, e_dist = self._edge_handles.closest(event.x, event.y) - self._set_cursor(e_dist <= self.grab_range) + self._set_span_cursor(enabled=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 + v = event.xdata vpress = self._eventpress.xdata else: - v = ydata + v = event.ydata vpress = self._eventpress.ydata # move existing span @@ -2992,7 +3223,7 @@ def __init__(self, ax, x, y, *, marker='o', marker_props=None, useblit=True): props = {'marker': marker, 'markersize': 7, 'markerfacecolor': 'w', 'linestyle': 'none', 'alpha': 0.5, 'visible': False, 'label': '_nolegend_', - **cbook.normalize_kwargs(marker_props, Line2D._alias_map)} + **cbook.normalize_kwargs(marker_props, Line2D)} self._markers = Line2D(x, y, animated=useblit, **props) self.ax.add_line(self._markers) @@ -3054,7 +3285,7 @@ def onselect(eclick: MouseEvent, erelease: MouseEvent) (when already existing) or cancelled. minspany : float, default: 0 - Selections with an y-span less than or equal to *minspanx* are removed + Selections with a y-span less than or equal to *minspanx* are removed (when already existing) or cancelled. useblit : bool, default: False @@ -3120,6 +3351,13 @@ def onselect(eclick: MouseEvent, erelease: MouseEvent) """ +class _RectangleSelectorAction(enum.Enum): + ROTATE = enum.auto() + MOVE = enum.auto() + RESIZE = enum.auto() + CREATE = enum.auto() + + @_docstring.Substitution(_RECTANGLESELECTOR_PARAMETERS_DOCSTRING.replace( '__ARTIST_NAME__', 'rectangle')) class RectangleSelector(_SelectorWidget): @@ -3176,7 +3414,7 @@ def __init__(self, ax, onselect=None, *, minspanx=0, if props is None: props = dict(facecolor='red', edgecolor='black', alpha=0.2, fill=True) - props = {**props, 'animated': self.useblit} + props = {**props, 'animated': self._useblit} self._visible = props.pop('visible', self._visible) to_draw = self._init_shape(**props) self.ax.add_patch(to_draw) @@ -3201,18 +3439,18 @@ def __init__(self, ax, onselect=None, *, minspanx=0, xc, yc = self.corners self._corner_handles = ToolHandles(self.ax, xc, yc, marker_props=self._handle_props, - useblit=self.useblit) + useblit=self._useblit) 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) + useblit=self._useblit) xc, yc = self.center self._center_handle = ToolHandles(self.ax, [xc], [yc], marker='s', marker_props=self._handle_props, - useblit=self.useblit) + useblit=self._useblit) self._active_handle = None @@ -3242,9 +3480,8 @@ def _press(self, event): 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.extents = event.xdata, event.xdata, event.ydata, event.ydata self._visible = True else: self.set_visible(True) @@ -3253,10 +3490,24 @@ def _press(self, event): self._rotation_on_press = self._rotation self._set_aspect_ratio_correction() + match self._get_action(): + case _RectangleSelectorAction.ROTATE: + # TODO: set to a rotate cursor if possible? + pass + case _RectangleSelectorAction.MOVE: + self._set_cursor(backend_tools.cursors.MOVE) + case _RectangleSelectorAction.RESIZE: + # TODO: set to a resize cursor if possible? + pass + case _RectangleSelectorAction.CREATE: + self._set_cursor(backend_tools.cursors.SELECT_REGION) + return False + @_call_with_reparented_event def _release(self, event): """Button release event handler.""" + self._set_cursor(backend_tools.Cursors.POINTER) if not self._interactive: self._selection_artist.set_visible(False) @@ -3300,9 +3551,20 @@ def _release(self, event): self.update() self._active_handle = None self._extents_on_press = None - return False + def _get_action(self): + state = self._state + if 'rotate' in state and self._active_handle in self._corner_order: + return _RectangleSelectorAction.ROTATE + elif self._active_handle == 'C': + return _RectangleSelectorAction.MOVE + elif self._active_handle: + return _RectangleSelectorAction.RESIZE + + return _RectangleSelectorAction.CREATE + + def _onmove(self, event): """ Motion notify event handler. @@ -3317,12 +3579,10 @@ def _onmove(self, event): # 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 + action = self._get_action() - xdata, ydata = self._get_data_coords(event) - if resize: + xdata, ydata = event.xdata, event.ydata + if action == _RectangleSelectorAction.RESIZE: inv_tr = self._get_rotation_transform().inverted() xdata, ydata = inv_tr.transform([xdata, ydata]) eventpress.xdata, eventpress.ydata = inv_tr.transform( @@ -3342,7 +3602,7 @@ def _onmove(self, event): x0, x1, y0, y1 = self._extents_on_press # rotate an existing shape - if rotate: + if action == _RectangleSelectorAction.ROTATE: # calculate angle abc a = (eventpress.xdata, eventpress.ydata) b = self.center @@ -3351,7 +3611,7 @@ def _onmove(self, event): np.arctan2(a[1]-b[1], a[0]-b[0])) self.rotation = np.rad2deg(self._rotation_on_press + angle) - elif resize: + elif action == _RectangleSelectorAction.RESIZE: size_on_press = [x1 - x0, y1 - y0] center = (x0 + size_on_press[0] / 2, y0 + size_on_press[1] / 2) @@ -3402,7 +3662,7 @@ def _onmove(self, event): sign = np.sign(xdata - x0) x1 = x0 + sign * abs(y1 - y0) * self._aspect_ratio_correction - elif move: + elif action == _RectangleSelectorAction.MOVE: x0, x1, y0, y1 = self._extents_on_press dx = xdata - eventpress.xdata dy = ydata - eventpress.ydata @@ -3697,7 +3957,7 @@ def __init__(self, ax, onselect=None, *, useblit=True, props=None, button=None): **(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, + 'animated': self._useblit, 'visible': False, } line = Line2D([], [], **props) self.ax.add_line(line) @@ -3707,6 +3967,7 @@ def _press(self, event): self.verts = [self._get_data(event)] self._selection_artist.set_visible(True) + @_call_with_reparented_event def _release(self, event): if self.verts is not None: self.verts.append(self._get_data(event)) @@ -3821,7 +4082,7 @@ def __init__(self, ax, onselect=None, *, useblit=False, if props is None: props = dict(color='k', linestyle='-', linewidth=2, alpha=0.5) - props = {**props, 'animated': self.useblit} + props = {**props, 'animated': self._useblit} self._selection_artist = line = Line2D([], [], **props) self.ax.add_line(line) @@ -3830,7 +4091,7 @@ def __init__(self, ax, onselect=None, *, useblit=False, markerfacecolor=props.get('color', 'k')) self._handle_props = handle_props self._polygon_handles = ToolHandles(self.ax, [], [], - useblit=self.useblit, + useblit=self._useblit, marker_props=self._handle_props) self._active_handle_idx = -1 @@ -3850,7 +4111,7 @@ def _get_bbox(self): def _add_box(self): self._box = RectangleSelector(self.ax, - useblit=self.useblit, + useblit=self._useblit, grab_range=self.grab_range, handle_props=self._box_handle_props, props=self._box_props, @@ -3877,6 +4138,7 @@ def _update_box(self): # Save a copy self._old_box_extents = self._box.extents + @_call_with_reparented_event def _scale_polygon(self, event): """ Scale the polygon selector points when the bounding box is moved or @@ -3941,6 +4203,7 @@ def _press(self, event): # support the 'move_all' state modifier). self._xys_at_press = self._xys.copy() + @_call_with_reparented_event def _release(self, event): """Button release event handler.""" # Release active tool handle. @@ -3960,11 +4223,12 @@ def _release(self, event): 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)) + self._xys.insert(-1, (event.xdata, event.ydata)) if self._selection_completed: self.onselect(self.verts) + @_call_with_reparented_event def onmove(self, event): """Cursor move event handler and validator.""" # Method overrides _SelectorWidget.onmove because the polygon selector @@ -3988,17 +4252,16 @@ def _onmove(self, event): # Move the active vertex (ToolHandle). if self._active_handle_idx >= 0: idx = self._active_handle_idx - self._xys[idx] = self._get_data_coords(event) + self._xys[idx] = (event.xdata, event.ydata) # 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._selection_completed: - self._xys[-1] = self._get_data_coords(event) + self._xys[-1] = (event.xdata, event.ydata) # Move all vertices. 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 + dx = event.xdata - self._eventpress.xdata + dy = event.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 @@ -4018,7 +4281,7 @@ def _onmove(self, event): if len(self._xys) > 3 and v0_dist < self.grab_range: self._xys[-1] = self._xys[0] else: - self._xys[-1] = self._get_data_coords(event) + self._xys[-1] = (event.xdata, event.ydata) self._draw_polygon() @@ -4040,12 +4303,12 @@ def _on_key_release(self, event): and (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._xys.append((event.xdata, event.ydata)) self._draw_polygon() # Reset the polygon if the released key is the 'clear' key. elif event.key == self._state_modifier_keys.get('clear'): event = self._clean_event(event) - self._xys = [self._get_data_coords(event)] + self._xys = [(event.xdata, event.ydata)] self._selection_completed = False self._remove_box() self.set_visible(True) @@ -4130,7 +4393,7 @@ class Lasso(AxesWidget): def __init__(self, ax, xy, callback, *, useblit=True, props=None): super().__init__(ax) - self.useblit = useblit and self.canvas.supports_blit + self.useblit = useblit and self.canvas.supports_blit # TODO: Make dynamic if self.useblit: self.background = self.canvas.copy_from_bbox(self.ax.bbox) @@ -4147,24 +4410,26 @@ def __init__(self, ax, xy, callback, *, useblit=True, props=None): self.connect_event('button_release_event', self.onrelease) self.connect_event('motion_notify_event', self.onmove) + @_call_with_reparented_event def onrelease(self, event): if self.ignore(event): return if self.verts is not None: - self.verts.append(self._get_data_coords(event)) + self.verts.append((event.xdata, event.ydata)) if len(self.verts) > 2: self.callback(self.verts) self.line.remove() self.verts = None self.disconnect_events() + @_call_with_reparented_event def onmove(self, event): if (self.ignore(event) or self.verts is None or event.button != 1 or not self.ax.contains(event)[0]): return - self.verts.append(self._get_data_coords(event)) + self.verts.append((event.xdata, event.ydata)) self.line.set_data(list(zip(*self.verts))) if self.useblit: diff --git a/lib/matplotlib/widgets.pyi b/lib/matplotlib/widgets.pyi index 0fcd1990e17e..a80ed8bf8274 100644 --- a/lib/matplotlib/widgets.pyi +++ b/lib/matplotlib/widgets.pyi @@ -6,6 +6,7 @@ from .figure import Figure from .lines import Line2D from .patches import Polygon, Rectangle from .text import Text +from .backend_tools import Cursors import PIL.Image @@ -38,6 +39,7 @@ class AxesWidget(Widget): def canvas(self) -> FigureCanvasBase | None: ... def connect_event(self, event: Event, callback: Callable) -> None: ... def disconnect_events(self) -> None: ... + def _set_cursor(self, cursor: Cursors) -> None: ... class Button(AxesWidget): label: Text @@ -64,7 +66,7 @@ class SliderBase(AxesWidget): valmax: float valstep: float | ArrayLike | None drag_active: bool - valfmt: str + valfmt: str | Callable[[float], str] | None def __init__( self, ax: Axes, @@ -73,7 +75,7 @@ class SliderBase(AxesWidget): closedmax: bool, valmin: float, valmax: float, - valfmt: str, + valfmt: str | Callable[[float], str] | None, dragging: Slider | None, valstep: float | ArrayLike | None, ) -> None: ... @@ -130,7 +132,7 @@ class RangeSlider(SliderBase): valmax: float, *, valinit: tuple[float, float] | None = ..., - valfmt: str | None = ..., + valfmt: str | Callable[[float], str] | None = ..., closedmin: bool = ..., closedmax: bool = ..., dragging: bool = ..., @@ -153,12 +155,13 @@ class CheckButtons(AxesWidget): labels: Sequence[str], actives: Iterable[bool] | None = ..., *, + layout: None | Literal["vertical", "horizontal"] | tuple[int, int] = None, useblit: bool = ..., - label_props: dict[str, Any] | None = ..., + label_props: dict[str, Sequence[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_label_props(self, props: dict[str, Sequence[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] @@ -199,6 +202,7 @@ class TextBox(AxesWidget): class RadioButtons(AxesWidget): activecolor: ColorType value_selected: str + index_selected: int labels: list[Text] def __init__( self, @@ -207,11 +211,12 @@ class RadioButtons(AxesWidget): active: int = ..., activecolor: ColorType | None = ..., *, + layout: None | Literal["vertical", "horizontal"] | tuple[int, int] = None, useblit: bool = ..., - label_props: dict[str, Any] | Sequence[dict[str, Any]] | None = ..., + label_props: dict[str, Sequence[Any]] | None = ..., radio_props: dict[str, Any] | None = ..., ) -> None: ... - def set_label_props(self, props: dict[str, Any]) -> None: ... + def set_label_props(self, props: dict[str, Sequence[Any]]) -> None: ... def set_radio_props(self, props: dict[str, Any]) -> None: ... def set_active(self, index: int) -> None: ... def clear(self) -> None: ... @@ -270,7 +275,7 @@ class MultiCursor(Widget): class _SelectorWidget(AxesWidget): onselect: Callable[[float, float], Any] - useblit: bool + _useblit: bool background: Any validButtons: list[MouseButton] def __init__( @@ -282,6 +287,8 @@ class _SelectorWidget(AxesWidget): state_modifier_keys: dict[str, str] | None = ..., use_data_coordinates: bool = ..., ) -> None: ... + @property + def useblit(self) -> bool: ... def update_background(self, event: Event) -> None: ... def connect_default_events(self) -> None: ... def ignore(self, event: Event) -> bool: ... @@ -335,6 +342,7 @@ class SpanSelector(_SelectorWidget): _props: dict[str, Any] | None = ..., _init: bool = ..., ) -> None: ... + def _set_span_cursor(self, *, enabled: bool) -> None: ... def connect_default_events(self) -> None: ... @property def direction(self) -> Literal["horizontal", "vertical"]: ... @@ -398,6 +406,7 @@ class RectangleSelector(_SelectorWidget): minspany: float spancoords: Literal["data", "pixels"] grab_range: float + _active_handle: None | Literal["C", "N", "NE", "E", "SE", "S", "SW", "W", "NW"] def __init__( self, ax: Axes, diff --git a/lib/mpl_toolkits/axes_grid1/anchored_artists.py b/lib/mpl_toolkits/axes_grid1/anchored_artists.py index 214b15843ebf..a8be06800a07 100644 --- a/lib/mpl_toolkits/axes_grid1/anchored_artists.py +++ b/lib/mpl_toolkits/axes_grid1/anchored_artists.py @@ -83,7 +83,7 @@ def __init__(self, transform, loc, ---------- 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`. loc : str Location of this artist. Valid locations are 'upper left', 'upper center', 'upper right', @@ -137,7 +137,7 @@ def __init__(self, transform, size, label, loc, ---------- 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*. @@ -256,7 +256,7 @@ def __init__(self, transform, label_x, label_y, length=0.15, ---------- 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 diff --git a/lib/mpl_toolkits/axes_grid1/axes_divider.py b/lib/mpl_toolkits/axes_grid1/axes_divider.py index 50365f482b72..f88b69dddea0 100644 --- a/lib/mpl_toolkits/axes_grid1/axes_divider.py +++ b/lib/mpl_toolkits/axes_grid1/axes_divider.py @@ -439,7 +439,7 @@ def append_axes(self, position, size, pad=None, *, axes_class=None, **kwargs All extra keywords arguments are passed to the created axes. """ - create_axes, pack_start = _api.check_getitem({ + create_axes, pack_start = _api.getitem_checked({ "left": (self.new_horizontal, True), "right": (self.new_horizontal, False), "bottom": (self.new_vertical, True), diff --git a/lib/mpl_toolkits/axes_grid1/axes_grid.py b/lib/mpl_toolkits/axes_grid1/axes_grid.py index 20abf18ea79c..b26c87edce1c 100644 --- a/lib/mpl_toolkits/axes_grid1/axes_grid.py +++ b/lib/mpl_toolkits/axes_grid1/axes_grid.py @@ -51,16 +51,17 @@ class Grid: in the usage pattern ``grid.axes_row[row][col]``. axes_llc : Axes The Axes in the lower left corner. - ngrids : int + n_axes : int Number of Axes in the grid. """ _defaultAxesClass = Axes + @_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, *, @@ -83,8 +84,8 @@ def __init__(self, fig, ``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, optional + If given, 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 @@ -116,14 +117,12 @@ def __init__(self, fig, """ 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: + if not 0 < n_axes <= self._nrows * self._ncols: raise ValueError( - "ngrids must be positive and not larger than nrows*ncols") - - self.ngrids = ngrids + "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)) @@ -150,7 +149,7 @@ def __init__(self, fig, 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] @@ -160,9 +159,9 @@ def __init__(self, fig, axes_array[row, col] = axes_class( fig, rect, sharex=sharex, sharey=sharey) self.axes_all = axes_array.ravel( - order="C" if self._direction == "row" else "F").tolist() - self.axes_column = axes_array.T.tolist() - self.axes_row = axes_array.tolist() + 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() @@ -177,7 +176,7 @@ def _init_locators(self): [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.ngrids): + for i in range(self.n_axes): col, row = self._get_col_row(i) self.axes_all[i].set_axes_locator( self._divider.new_locator(nx=2 * col, ny=2 * (self._nrows - 1 - row))) @@ -190,6 +189,9 @@ def _get_col_row(self, n): return col, row + n_axes = property(lambda self: len(self.axes_all)) + ngrids = _api.deprecated('3.11')(property(lambda self: len(self.axes_all))) + # Good to propagate __len__ if we have __getitem__ def __len__(self): return len(self.axes_all) @@ -251,28 +253,27 @@ def set_label_mode(self, mode): - "keep": Do not do anything. """ _api.check_in_list(["all", "L", "1", "keep"], mode=mode) - is_last_row, is_first_col = ( - np.mgrid[:self._nrows, :self._ncols] == [[[self._nrows - 1]], [[0]]]) - if mode == "all": - bottom = left = np.full((self._nrows, self._ncols), True) - elif mode == "L": - bottom = is_last_row - left = is_first_col - elif mode == "1": - bottom = left = is_last_row & is_first_col - else: + if mode == "keep": return - for i in range(self._nrows): - for j in range(self._ncols): + for i, j in np.ndindex(self._nrows, self._ncols): + try: ax = self.axes_row[i][j] - 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"] - bottom_axis.toggle(ticklabels=bottom[i, j], label=bottom[i, j]) - left_axis.toggle(ticklabels=left[i, j], label=left[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 @@ -297,7 +298,7 @@ class ImageGrid(Grid): def __init__(self, fig, rect, nrows_ncols, - ngrids=None, + n_axes=None, direction="row", axes_pad=0.02, *, @@ -321,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, optional + If given, 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 @@ -349,7 +350,7 @@ 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. @@ -358,12 +359,12 @@ def __init__(self, fig, ``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%" + 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 + axes_class : subclass of `matplotlib.axes.Axes`, default: `.mpl_axes.Axes` """ _api.check_in_list(["each", "single", "edge", None], cbar_mode=cbar_mode) @@ -376,7 +377,7 @@ def __init__(self, fig, # The colorbar axes are created in _init_locators(). super().__init__( - fig, rect, nrows_ncols, ngrids, + 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) @@ -412,7 +413,7 @@ def _init_locators(self): _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 @@ -433,7 +434,7 @@ 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) @@ -494,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]) @@ -534,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"): @@ -548,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_size.py b/lib/mpl_toolkits/axes_grid1/axes_size.py index 86e5f70d9824..55820827cd6a 100644 --- a/lib/mpl_toolkits/axes_grid1/axes_size.py +++ b/lib/mpl_toolkits/axes_grid1/axes_size.py @@ -1,7 +1,7 @@ """ 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, +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 diff --git a/lib/mpl_toolkits/axes_grid1/inset_locator.py b/lib/mpl_toolkits/axes_grid1/inset_locator.py index 52fe6efc0618..a1a9cc8df591 100644 --- a/lib/mpl_toolkits/axes_grid1/inset_locator.py +++ b/lib/mpl_toolkits/axes_grid1/inset_locator.py @@ -341,11 +341,16 @@ def inset_axes(parent_axes, width, height, loc='upper right', %(Axes:kwdoc)s - borderpad : float, default: 0.5 + borderpad : float or (float, float), default: 0.5 Padding between inset axes and the bbox_to_anchor. + If a float, the same padding is used for both x and y. + If a tuple of two floats, it specifies the (x, y) padding. The units are axes font size, i.e. for a default font size of 10 points *borderpad = 0.5* is equivalent to a padding of 5 points. + .. versionadded:: 3.11 + The *borderpad* parameter now accepts a tuple of (x, y) paddings. + Returns ------- inset_axes : *axes_class* diff --git a/lib/mpl_toolkits/axes_grid1/parasite_axes.py b/lib/mpl_toolkits/axes_grid1/parasite_axes.py index f7bc2df6d7e0..fbc6e8141272 100644 --- a/lib/mpl_toolkits/axes_grid1/parasite_axes.py +++ b/lib/mpl_toolkits/axes_grid1/parasite_axes.py @@ -25,6 +25,9 @@ def clear(self): self._parent_axes.callbacks._connect_picklable( "ylim_changed", self._sync_lims) + 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 # to the factory), which only handles pick events registered on the 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 index 31c63d7df718..ad293669c14c 100644 Binary files a/lib/mpl_toolkits/axes_grid1/tests/baseline_images/test_axes_grid1/anchored_locator_base_call.png 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/axes_grid1/tests/baseline_images/test_axes_grid1/image_grid.png b/lib/mpl_toolkits/axes_grid1/tests/baseline_images/test_axes_grid1/image_grid.png index a696787a0248..5ba5f11a4876 100644 Binary files a/lib/mpl_toolkits/axes_grid1/tests/baseline_images/test_axes_grid1/image_grid.png and b/lib/mpl_toolkits/axes_grid1/tests/baseline_images/test_axes_grid1/image_grid.png differ 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 index f9a4524b5812..958c8b53b320 100644 Binary files a/lib/mpl_toolkits/axes_grid1/tests/baseline_images/test_axes_grid1/image_grid_each_left_label_mode_all.png 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/imagegrid_cbar_mode.png b/lib/mpl_toolkits/axes_grid1/tests/baseline_images/test_axes_grid1/imagegrid_cbar_mode.png index 9cb576faa49a..e855c0cc7b0c 100644 Binary files a/lib/mpl_toolkits/axes_grid1/tests/baseline_images/test_axes_grid1/imagegrid_cbar_mode.png and b/lib/mpl_toolkits/axes_grid1/tests/baseline_images/test_axes_grid1/imagegrid_cbar_mode.png differ diff --git a/lib/mpl_toolkits/axes_grid1/tests/baseline_images/test_axes_grid1/inset_axes.png b/lib/mpl_toolkits/axes_grid1/tests/baseline_images/test_axes_grid1/inset_axes.png index 90498f5d441b..2c717df0f06a 100644 Binary files a/lib/mpl_toolkits/axes_grid1/tests/baseline_images/test_axes_grid1/inset_axes.png and b/lib/mpl_toolkits/axes_grid1/tests/baseline_images/test_axes_grid1/inset_axes.png differ diff --git a/lib/mpl_toolkits/axes_grid1/tests/baseline_images/test_axes_grid1/inset_locator.png b/lib/mpl_toolkits/axes_grid1/tests/baseline_images/test_axes_grid1/inset_locator.png index c7ad1e64b84d..6329a459b0d9 100644 Binary files a/lib/mpl_toolkits/axes_grid1/tests/baseline_images/test_axes_grid1/inset_locator.png and b/lib/mpl_toolkits/axes_grid1/tests/baseline_images/test_axes_grid1/inset_locator.png differ 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 index 5cf6dc7e35c0..0159cf22c62d 100644 Binary files a/lib/mpl_toolkits/axes_grid1/tests/baseline_images/test_axes_grid1/rgb_axes.png and b/lib/mpl_toolkits/axes_grid1/tests/baseline_images/test_axes_grid1/rgb_axes.png differ diff --git a/lib/mpl_toolkits/axes_grid1/tests/conftest.py b/lib/mpl_toolkits/axes_grid1/tests/conftest.py index 61c2de3e07ba..12eaac9ce2f4 100644 --- a/lib/mpl_toolkits/axes_grid1/tests/conftest.py +++ b/lib/mpl_toolkits/axes_grid1/tests/conftest.py @@ -1,2 +1,3 @@ -from matplotlib.testing.conftest import (mpl_test_settings, # noqa - pytest_configure, pytest_unconfigure) +from matplotlib.testing.conftest import ( # noqa + pytest_configure, pytest_unconfigure, + high_memory, mpl_test_settings) diff --git a/lib/mpl_toolkits/axes_grid1/tests/test_axes_grid1.py b/lib/mpl_toolkits/axes_grid1/tests/test_axes_grid1.py index 778bd9ca04d0..7f54466a3cce 100644 --- a/lib/mpl_toolkits/axes_grid1/tests/test_axes_grid1.py +++ b/lib/mpl_toolkits/axes_grid1/tests/test_axes_grid1.py @@ -9,7 +9,7 @@ from matplotlib.backend_bases import MouseEvent from matplotlib.colors import LogNorm from matplotlib.patches import Circle, Ellipse -from matplotlib.transforms import Bbox, TransformedBbox +from matplotlib.transforms import Affine2D, Bbox, TransformedBbox from matplotlib.testing.decorators import ( check_figures_equal, image_comparison, remove_ticks_and_titles) @@ -26,6 +26,7 @@ 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 @@ -61,7 +62,7 @@ def test_divider_append_axes(): # Update style when regenerating the test image -@image_comparison(['twin_axes_empty_and_removed'], extensions=["png"], tol=1, +@image_comparison(['twin_axes_empty_and_removed.png'], tol=1, style=('classic', '_classic_test_patch')) def test_twin_axes_empty_and_removed(): # Purely cosmetic font changes (avoid overlap) @@ -92,8 +93,8 @@ def test_twin_axes_empty_and_removed(): def test_twin_axes_both_with_units(): host = host_subplot(111) - with pytest.warns(mpl.MatplotlibDeprecationWarning): - host.plot_date([0, 1, 2], [0, 1, 2], xdate=False, ydate=True) + 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" @@ -104,7 +105,6 @@ def test_axesgrid_colorbar_log_smoketest(): 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", @@ -346,7 +346,7 @@ def test_fill_facecolor(): # Update style when regenerating the test image @image_comparison(['zoomed_axes.png', 'inverted_zoomed_axes.png'], style=('classic', '_classic_test_patch'), - tol=0.02 if platform.machine() == 'arm64' else 0) + 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]) @@ -468,6 +468,26 @@ def test_gettightbbox(): [-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"), @@ -638,15 +658,15 @@ def test_grid_axes_position(direction): assert loc[3].args[1] == loc[2].args[1] -@pytest.mark.parametrize('rect, ngrids, error, message', ( +@pytest.mark.parametrize('rect, n_axes, error, message', ( ((1, 1), None, TypeError, "Incorrect rect format"), - (111, -1, ValueError, "ngrids must be positive"), - (111, 7, ValueError, "ngrids must be positive"), + (111, -1, ValueError, "n_axes must be positive"), + (111, 7, ValueError, "n_axes must be positive"), )) -def test_grid_errors(rect, ngrids, error, message): +def test_grid_errors(rect, n_axes, error, message): fig = plt.figure() with pytest.raises(error, match=message): - Grid(fig, rect, (2, 3), ngrids=ngrids) + Grid(fig, rect, (2, 3), n_axes=n_axes) @pytest.mark.parametrize('anchor, error, message', ( @@ -661,7 +681,7 @@ def test_divider_errors(anchor, error, message): anchor=anchor) -@check_figures_equal(extensions=["png"]) +@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]) @@ -679,7 +699,7 @@ def test_mark_inset_unstales_viewlim(fig_test, fig_ref): def test_auto_adjustable(): fig = plt.figure() - ax = fig.add_axes([0, 0, 1, 1]) + ax = fig.add_axes((0, 0, 1, 1)) pad = 0.1 make_axes_area_auto_adjustable(ax, pad=pad) fig.canvas.draw() @@ -780,3 +800,11 @@ def test_anchored_locator_base_call(): 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 + with pytest.warns(mpl.MatplotlibDeprecationWarning, match="ngrids attribute"): + assert grid.ngrids == 5 diff --git a/lib/mpl_toolkits/axisartist/angle_helper.py b/lib/mpl_toolkits/axisartist/angle_helper.py index 1786cd70bcdb..56b461e4a1d3 100644 --- a/lib/mpl_toolkits/axisartist/angle_helper.py +++ b/lib/mpl_toolkits/axisartist/angle_helper.py @@ -1,6 +1,7 @@ import numpy as np import math +from matplotlib.transforms import Bbox from mpl_toolkits.axisartist.grid_finder import ExtremeFinderSimple @@ -347,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. @@ -367,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: @@ -391,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/axis_artist.py b/lib/mpl_toolkits/axisartist/axis_artist.py index b416d56abe6b..3ba70c3d7d3b 100644 --- a/lib/mpl_toolkits/axisartist/axis_artist.py +++ b/lib/mpl_toolkits/axisartist/axis_artist.py @@ -7,7 +7,7 @@ There is one `AxisArtist` per Axis; it can be accessed through the ``axis`` dictionary of the parent Axes (which should be a -`mpl_toolkits.axislines.Axes`), e.g. ``ax.axis["bottom"]``. +`~mpl_toolkits.axisartist.axislines.Axes`), e.g. ``ax.axis["bottom"]``. Children of the AxisArtist are accessed as attributes: ``.line`` and ``.label`` for the axis line and label, ``.major_ticks``, ``.major_ticklabels``, @@ -55,14 +55,14 @@ axislabel ha right center right center =================== ====== ======== ====== ======== -Ticks are by default direct opposite side of the ticklabels. To make ticks to -the same side of the ticklabels, :: +Tick orientation is controlled by :rc:`xtick.direction` and +:rc:`ytick.direction`; they can be manually adjusted using :: - ax.axis["bottom"].major_ticks.set_tick_out(True) + ax.axis["bottom"].major_ticks.set_tick_direction("in") # or "out", "inout" The following attributes can be customized (use the ``set_xxx`` methods): -* `Ticks`: ticksize, tick_out +* `Ticks`: ticksize, tick_direction * `TickLabels`: pad * `AxisLabel`: pad """ @@ -109,16 +109,16 @@ class Ticks(AttributeCopier, Line2D): 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 - in opposite direction of ticklabels by default), use - ``set_tick_out(False)`` + To change the tick size (length), use `set_ticksize`. + To change the direction of the ticks, use ``set_tick_direction("in")`` (or + "out", or "inout"). """ + locs_angles_labels = _api.deprecated("3.11")(property(lambda self: [])) + + @_api.delete_parameter("3.11", "tick_out", alternative="tick_direction") def __init__(self, ticksize, tick_out=False, *, axis=None, **kwargs): self._ticksize = ticksize - self.locs_angles_labels = [] - self.set_tick_out(tick_out) self._axis = axis @@ -152,13 +152,33 @@ def get_markeredgecolor(self): def get_markeredgewidth(self): return self.get_attribute_from_ref_artist("markeredgewidth") + def set_tick_direction(self, direction): + _api.check_in_list(["in", "out", "inout"], direction=direction) + self._tick_dir = direction + + def get_tick_direction(self): + return self._tick_dir + def set_tick_out(self, b): - """Set whether ticks are drawn inside or outside the axes.""" - self._tick_out = b + """ + Set whether ticks are drawn inside or outside the axes. + .. admonition:: Discouraged + Consider using the more general method `.set_tick_direction` instead. + """ + self._tick_dir = "out" if b else "in" + + @_api.deprecated("3.11", alternative="get_tick_direction") def get_tick_out(self): """Return whether ticks are drawn inside or outside the axes.""" - return self._tick_out + if self._tick_dir == "in": + return False + elif self._tick_dir == "out": + return True + else: + raise ValueError( + f"Tick direction ({self._tick_dir!r}) not supported by get_tick_out, " + f"use get_tick_direction instead") def set_ticksize(self, ticksize): """Set length of the ticks in points.""" @@ -171,29 +191,33 @@ def get_ticksize(self): def set_locs_angles(self, locs_angles): self.locs_angles = locs_angles - _tickvert_path = Path([[0., 0.], [1., 0.]]) + _tick_paths = { + "in": Path([[0, 0], [+1, 0]]), + "inout": Path([[-1/2, 0.], [+1/2, 0]]), + "out": Path([[-1, 0], [0, 0]]), + } def draw(self, renderer): if not self.get_visible(): return gc = renderer.new_gc() - gc.set_foreground(self.get_markeredgecolor()) + edgecolor = mcolors.to_rgba(self.get_markeredgecolor()) + gc.set_foreground(edgecolor, isRGBA=True) gc.set_linewidth(self.get_markeredgewidth()) gc.set_alpha(self._alpha) + tickvert_path = self._tick_paths[self._tick_dir] path_trans = self.get_transform() marker_transform = (Affine2D() .scale(renderer.points_to_pixels(self._ticksize))) - if self.get_tick_out(): - marker_transform.rotate_deg(180) for loc, angle in self.locs_angles: locs = path_trans.transform_non_affine(np.array([loc])) if self.axes and not self.axes.viewLim.contains(*locs[0]): continue renderer.draw_markers( - gc, self._tickvert_path, + gc, tickvert_path, marker_transform + Affine2D().rotate_deg(angle), Path(locs), path_trans.get_affine()) @@ -207,8 +231,9 @@ class LabelBase(mtext.Text): text_ref_angle, and offset_radius attributes. """ + locs_angles_labels = _api.deprecated("3.11")(property(lambda self: [])) + def __init__(self, *args, **kwargs): - self.locs_angles_labels = [] self._ref_angle = 0 self._offset_radius = 0. @@ -334,7 +359,7 @@ def set_default_alignment(self, d): ---------- d : {"left", "bottom", "right", "top"} """ - va, ha = _api.check_getitem(self._default_alignments, d=d) + va, ha = _api.getitem_checked(self._default_alignments, d=d) self.set_va(va) self.set_ha(ha) @@ -351,7 +376,7 @@ def set_default_angle(self, d): ---------- d : {"left", "bottom", "right", "top"} """ - self.set_rotation(_api.check_getitem(self._default_angles, d=d)) + self.set_rotation(_api.getitem_checked(self._default_angles, d=d)) def set_axis_direction(self, d): """ @@ -588,8 +613,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 @@ -764,7 +790,7 @@ def set_ticklabel_direction(self, tick_direction): ---------- tick_direction : {"+", "-"} """ - self._ticklabel_add_angle = _api.check_getitem( + self._ticklabel_add_angle = _api.getitem_checked( {"+": 0, "-": 180}, tick_direction=tick_direction) def invert_ticklabel_direction(self): @@ -783,7 +809,7 @@ def set_axislabel_direction(self, label_direction): ---------- label_direction : {"+", "-"} """ - self._axislabel_add_angle = _api.check_getitem( + self._axislabel_add_angle = _api.getitem_checked( {"+": 0, "-": 180}, label_direction=label_direction) def get_transform(self): @@ -865,14 +891,16 @@ def _init_ticks(self, **kwargs): + self.offset_transform) self.major_ticks = Ticks( - kwargs.get( + ticksize=kwargs.get( "major_tick_size", mpl.rcParams[f"{axis_name}tick.major.size"]), + tick_direction=mpl.rcParams[f"{axis_name}tick.direction"], axis=self.axis, transform=trans) self.minor_ticks = Ticks( - kwargs.get( + ticksize=kwargs.get( "minor_tick_size", mpl.rcParams[f"{axis_name}tick.minor.size"]), + tick_direction=mpl.rcParams[f"{axis_name}tick.direction"], axis=self.axis, transform=trans) size = mpl.rcParams[f"{axis_name}tick.labelsize"] @@ -924,14 +952,13 @@ def _update_ticks(self, renderer=None): 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(): - 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._external_pad = 0 - self.minor_ticklabels._external_pad = 0 + self.major_ticklabels._external_pad = \ + self.minor_ticklabels._external_pad = ( + renderer.points_to_pixels(self.major_ticks._ticksize) + * {"in": 0, "inout": 1/2, "out": 1}[ + self.major_ticks.get_tick_direction()] + * self.major_ticks.get_visible() # 0 if invisible. + ) majortick_iter, minortick_iter = \ self._axis_artist_helper.get_tick_iterators(self.axes) @@ -1006,13 +1033,18 @@ def _update_label(self, renderer): 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 + axislabel_pad = max( + # major pad: + self.major_ticks._ticksize + * {"in": 1, "inout": 1/2, "out": 0}[ + self.major_ticks.get_tick_direction()] + * self.major_ticks.get_visible(), # 0 if invisible. + # minor pad: + self.minor_ticks._ticksize + * {"in": 1, "inout": 1/2, "out": 0}[ + self.minor_ticks.get_tick_direction()] + * self.minor_ticks.get_visible(), # 0 if invisible. + ) else: axislabel_pad = max(self.major_ticklabels._axislabel_pad, self.minor_ticklabels._axislabel_pad) diff --git a/lib/mpl_toolkits/axisartist/axisline_style.py b/lib/mpl_toolkits/axisartist/axisline_style.py index 7f25b98082ef..ac89603e0844 100644 --- a/lib/mpl_toolkits/axisartist/axisline_style.py +++ b/lib/mpl_toolkits/axisartist/axisline_style.py @@ -177,8 +177,7 @@ def __init__(self, size=1, facecolor=None): .. versionadded:: 3.7 """ - if facecolor is None: - facecolor = mpl.rcParams['axes.edgecolor'] + facecolor = mpl._val_or_rc(facecolor, 'axes.edgecolor') self.size = size self._facecolor = facecolor super().__init__(size=size) diff --git a/lib/mpl_toolkits/axisartist/axislines.py b/lib/mpl_toolkits/axisartist/axislines.py index 8d06cb236269..68091c25ec3f 100644 --- a/lib/mpl_toolkits/axisartist/axislines.py +++ b/lib/mpl_toolkits/axisartist/axislines.py @@ -16,7 +16,7 @@ In the new axes class, xaxis and yaxis is set to not visible by default, and new set of artist (AxisArtist) are defined to draw axis line, ticks, ticklabels and axis label. Axes.axis attribute serves as -a dictionary of these artists, i.e., ax.axis["left"] is a AxisArtist +a dictionary of these artists, i.e., ax.axis["left"] is an AxisArtist instance responsible to draw left y-axis. The default Axes.axis contains "bottom", "left", "top" and "right". @@ -45,6 +45,8 @@ 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 # noqa from .axis_artist import AxisArtist, GridlinesCollection @@ -118,10 +120,9 @@ def _to_xy(self, values, const): class _FixedAxisArtistHelperBase(_AxisArtistHelperBase): """Helper class for a fixed (in the axes coordinate) axis.""" - @_api.delete_parameter("3.9", "nth_coord") - def __init__(self, loc, nth_coord=None): + def __init__(self, loc): """``nth_coord = 0``: x-axis; ``nth_coord = 1``: y-axis.""" - super().__init__(_api.check_getitem( + super().__init__(_api.getitem_checked( {"bottom": 0, "top": 0, "left": 1, "right": 1}, loc=loc)) self._loc = loc self._pos = {"bottom": 0, "top": 1, "left": 0, "right": 1}[loc] @@ -169,12 +170,7 @@ def get_line(self, axes): class FixedAxisArtistHelperRectilinear(_FixedAxisArtistHelperBase): - @_api.delete_parameter("3.9", "nth_coord") - 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 - """ + def __init__(self, axes, loc): super().__init__(loc) self.axis = [axes.xaxis, axes.yaxis][self.nth_coord] @@ -285,10 +281,10 @@ def update_lim(self, axes): x1, x2 = axes.get_xlim() y1, y2 = axes.get_ylim() if self._old_limits != (x1, x2, y1, y2): - self._update_grid(x1, y1, x2, y2) + self._update_grid(Bbox.from_extents(x1, y1, x2, y2)) self._old_limits = (x1, x2, y1, y2) - def _update_grid(self, x1, y1, x2, y2): + def _update_grid(self, bbox): """Cache relevant computations when the axes limits have changed.""" def get_gridlines(self, which, axis): @@ -309,10 +305,9 @@ def __init__(self, axes): super().__init__() self.axes = axes - @_api.delete_parameter( - "3.9", "nth_coord", addendum="'nth_coord' is now inferred from 'loc'.") def new_fixed_axis( - self, loc, nth_coord=None, axis_direction=None, offset=None, axes=None): + self, loc, *, axis_direction=None, offset=None, axes=None + ): if axes is None: _api.warn_external( "'new_fixed_axis' explicitly requires the axes keyword.") diff --git a/lib/mpl_toolkits/axisartist/floating_axes.py b/lib/mpl_toolkits/axisartist/floating_axes.py index 74e4c941879b..aa8a2129aae7 100644 --- a/lib/mpl_toolkits/axisartist/floating_axes.py +++ b/lib/mpl_toolkits/axisartist/floating_axes.py @@ -13,9 +13,8 @@ from matplotlib import _api, cbook import matplotlib.patches as mpatches from matplotlib.path import Path - +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 @@ -34,7 +33,7 @@ def __init__(self, grid_helper, side, nth_coord_ticks=None): nth_coord = 0 -> x axis, nth_coord = 1 -> y axis """ lon1, lon2, lat1, lat2 = grid_helper.grid_finder.extreme_finder(*[None] * 5) - value, nth_coord = _api.check_getitem( + value, nth_coord = _api.getitem_checked( 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) @@ -71,25 +70,19 @@ def trf_xy(x, y): if self.nth_coord == 0: mask = (ymin <= yy0) & (yy0 <= ymax) - (xx1, yy1), (dxx1, dyy1), (dxx2, dyy2) = \ - grid_helper_curvelinear._value_and_jacobian( + (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) - (xx1, yy1), (dxx2, dyy2), (dxx1, dyy1) = \ - grid_helper_curvelinear._value_and_jacobian( + (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 = [l for l, m in zip(labels, mask) if m] - - angle_normal = np.arctan2(dyy1, dxx1) - angle_tangent = np.arctan2(dyy2, dxx2) - mm = (dyy1 == 0) & (dxx1 == 0) # points with degenerate normal - angle_normal[mm] = angle_tangent[mm] + np.pi / 2 - tick_to_axes = self.get_tick_transform(axes) - axes.transAxes in_01 = functools.partial( mpl.transforms._interval_contains_close, (0, 1)) @@ -109,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): @@ -125,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): @@ -177,25 +170,22 @@ def new_fixed_axis( # axis.get_helper().set_extremes(*self._extremes[2:]) # return axis - def _update_grid(self, x1, y1, x2, y2): + def _update_grid(self, bbox): if self._grid_info is None: self._grid_info = dict() 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, lon_max = sorted(extremes[:2]) - lat_min, lat_max = sorted(extremes[2:]) - grid_info["extremes"] = lon_min, lon_max, lat_min, lat_max # extremes + lon_min, lat_min, lon_max, lat_max = tbbox.extents + grid_info["extremes"] = tbbox - lon_levs, lon_n, lon_factor = \ - grid_finder.grid_locator1(lon_min, lon_max) + 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, 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 @@ -212,14 +202,13 @@ def _update_grid(self, x1, y1, x2, y2): 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 @@ -227,9 +216,9 @@ 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 diff --git a/lib/mpl_toolkits/axisartist/grid_finder.py b/lib/mpl_toolkits/axisartist/grid_finder.py index ff67aa6e8720..1f86757df7b8 100644 --- a/lib/mpl_toolkits/axisartist/grid_finder.py +++ b/lib/mpl_toolkits/axisartist/grid_finder.py @@ -36,14 +36,10 @@ def _find_line_box_crossings(xys, bbox): for u0, inside in [(umin, us > umin), (umax, us < umax)]: cross = [] idxs, = (inside[:-1] ^ inside[1:]).nonzero() - for idx in idxs: - v = vs[idx] + (u0 - us[idx]) * dvs[idx] / dus[idx] - if not vmin <= v <= vmax: - continue - crossing = (u0, v)[sl] - theta = np.degrees(np.arctan2(*dxys[idx][::-1])) - cross.append((crossing, theta)) - crossings.append(cross) + 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 @@ -77,20 +73,29 @@ 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 _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 _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): @@ -159,53 +164,52 @@ def _format_ticks(self, idx, direction, factor, levels): 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( + fmt = _api.getitem_checked( {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): + def get_grid_info(self, *args, **kwargs): """ - lon_values, lat_values : list of grid values. if integer is given, - rough number of grids in each direction. + Compute positioning information for grid lines and ticks, given the + axes' data *bbox*. """ + params = _api.select_matching_signature( + [lambda x1, y1, x2, y2: locals(), lambda bbox: locals()], *args, **kwargs) + if "x1" in params: + _api.warn_deprecated("3.11", message=( + "Passing extents as separate arguments to get_grid_info is deprecated " + "since %(since)s and support will be removed %(removal)s; pass a " + "single bbox instead.")) + bbox = Bbox.from_extents( + params["x1"], params["y1"], params["x2"], params["y2"]) + else: + bbox = params["bbox"] - 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) - lon_levs = np.asarray(lon_levs) - lat_levs, lat_n, lat_factor = self.grid_locator2(lat_min, lat_max) - lat_levs = np.asarray(lat_levs) + tbbox = self.extreme_finder._find_transformed_bbox( + self.get_transform().inverted(), bbox) - lon_values = lon_levs[:lon_n] / lon_factor - lat_values = lat_levs[:lat_n] / lat_factor + lon_levs, lon_n, lon_factor = self.grid_locator1(*tbbox.intervalx) + lat_levs, lat_n, lat_factor = self.grid_locator2(*tbbox.intervaly) - lon_lines, lat_lines = self._get_raw_grid_lines(lon_values, - lat_values, - lon_min, lon_max, - lat_min, lat_max) + lon_values = np.asarray(lon_levs[:lon_n]) / lon_factor + lat_values = np.asarray(lat_levs[:lat_n]) / lat_factor - bb = Bbox.from_extents(x1, y1, x2, y2).expanded(1 + 2e-10, 1 + 2e-10) + lon_lines, lat_lines = self._get_raw_grid_lines(lon_values, lat_values, tbbox) - grid_info = { - "extremes": extremes, - # "lon", "lat", filled below. - } + 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": [[l] for l in lines], + "lines": lines, "ticks": {"left": [], "right": [], "bottom": [], "top": []}, } - for (lx, ly), v, level in zip(lines, values, levs): - all_crossings = _find_line_box_crossings(np.column_stack([lx, ly]), bb) + 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: @@ -218,18 +222,14 @@ def get_grid_info(self, x1, y1, x2, y2): 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 set_transform(self, aux_trans): @@ -246,9 +246,11 @@ def get_transform(self): update_transform = set_transform # backcompat alias. + @_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 + @_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 diff --git a/lib/mpl_toolkits/axisartist/grid_helper_curvelinear.py b/lib/mpl_toolkits/axisartist/grid_helper_curvelinear.py index a7eb9d5cfe21..aa37a3680fa5 100644 --- a/lib/mpl_toolkits/axisartist/grid_helper_curvelinear.py +++ b/lib/mpl_toolkits/axisartist/grid_helper_curvelinear.py @@ -7,38 +7,83 @@ import numpy as np import matplotlib as mpl -from matplotlib import _api from matplotlib.path import Path -from matplotlib.transforms import Affine2D, IdentityTransform +from matplotlib.transforms import Affine2D, Bbox, IdentityTransform from .axislines import ( _FixedAxisArtistHelperBase, _FloatingAxisArtistHelperBase, GridHelperBase) from .axis_artist import AxisArtist from .grid_finder import GridFinder -def _value_and_jacobian(func, xs, ys, xlims, ylims): +def _value_and_jac_angle(func, xs, ys, xlim, ylim): """ - Compute *func* and its derivatives along x and y at positions *xs*, *ys*, - while ensuring that finite difference calculations don't try to evaluate - values outside of *xlims*, *ylims*. + 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. """ - eps = np.finfo(float).eps ** (1/2) # see e.g. scipy.optimize.approx_fprime + + shape = np.broadcast_shapes(np.shape(xs), np.shape(ys)) val = func(xs, ys) - # Take the finite difference step in the direction where the bound is the - # furthest; the step size is min of epsilon and distance to that bound. - xlo, xhi = sorted(xlims) - dxlo = xs - xlo - dxhi = xhi - xs - xeps = (np.take([-1, 1], dxhi >= dxlo) - * np.minimum(eps, np.maximum(dxlo, dxhi))) - val_dx = func(xs + xeps, ys) - ylo, yhi = sorted(ylims) - dylo = ys - ylo - dyhi = yhi - ys - yeps = (np.take([-1, 1], dyhi >= dylo) - * np.minimum(eps, np.maximum(dylo, dyhi))) - val_dy = func(xs, ys + yeps) - return (val, (val_dx - val) / xeps, (val_dy - val) / yeps) + + # 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): @@ -115,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) @@ -127,29 +172,29 @@ 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) - 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) - xx, yy = grid_finder.transform_xy(xx0, yy0) + 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": (lon_min, lon_max, lat_min, lat_max), + "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": (xx, yy), + "line_xy": xys, } def get_axislabel_transform(self, axes): @@ -160,19 +205,18 @@ def trf_xy(x, y): trf = self.grid_helper.grid_finder.get_transform() + axes.transData return trf.transform([x, y]).T - xmin, xmax, ymin, ymax = self._grid_info["extremes"] + xmin, ymin, xmax, ymax = self._grid_info["extremes"].extents if self.nth_coord == 0: xx0 = self.value yy0 = (ymin + ymax) / 2 elif self.nth_coord == 1: xx0 = (xmin + xmax) / 2 yy0 = self.value - xy1, dxy1_dx, dxy1_dy = _value_and_jacobian( + 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: - d = [dxy1_dy, dxy1_dx][self.nth_coord] - return xy1, np.rad2deg(np.arctan2(*d[::-1])) + return xy1, np.rad2deg([angle_dy, angle_dx][self.nth_coord]) else: return None, None @@ -197,23 +241,17 @@ def trf_xy(x, y): # find angles if self.nth_coord == 0: mask = (e0 <= yy0) & (yy0 <= e1) - (xx1, yy1), (dxx1, dyy1), (dxx2, dyy2) = _value_and_jacobian( + (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: mask = (e0 <= xx0) & (xx0 <= e1) - (xx1, yy1), (dxx2, dyy2), (dxx1, dyy1) = _value_and_jacobian( + (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] - - angle_normal = np.arctan2(dyy1, dxx1) - angle_tangent = np.arctan2(dyy2, dxx2) - mm = (dyy1 == 0) & (dxx1 == 0) # points with degenerate normal - angle_normal[mm] = angle_tangent[mm] + np.pi / 2 - tick_to_axes = self.get_tick_transform(axes) - axes.transAxes in_01 = functools.partial( mpl.transforms._interval_contains_close, (0, 1)) @@ -232,8 +270,7 @@ def get_line_transform(self, axes): def get_line(self, axes): self.update_lim(axes) - x, y = self._grid_info["line_xy"] - return Path(np.column_stack([x, y])) + return Path(self._grid_info["line_xy"]) class GridHelperCurveLinear(GridHelperBase): @@ -278,9 +315,9 @@ def update_grid_finder(self, aux_trans=None, **kwargs): self.grid_finder.update(**kwargs) self._old_limits = None # Force revalidation. - @_api.make_keyword_only("3.9", "nth_coord") def new_fixed_axis( - self, loc, nth_coord=None, axis_direction=None, offset=None, axes=None): + self, loc, *, axis_direction=None, offset=None, axes=None, nth_coord=None + ): if axes is None: axes = self.axes if axis_direction is None: @@ -303,26 +340,13 @@ def new_floating_axis(self, nth_coord, value, axes=None, axis_direction="bottom" # 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) 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 - - @_api.deprecated("3.9") - def get_tick_iterator(self, nth_coord, axis_side, minor=False): - angle_tangent = dict(left=90, right=90, bottom=0, top=0)[axis_side] - lon_or_lat = ["lon", "lat"][nth_coord] - if not minor: # major ticks - for tick in self._grid_info[lon_or_lat]["ticks"][axis_side]: - yield *tick["loc"], angle_tangent, tick["label"] - else: - for tick in self._grid_info[lon_or_lat]["ticks"][axis_side]: - yield *tick["loc"], angle_tangent, "" 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 index 77314c1695a0..3b2b80f1f678 100644 Binary files a/lib/mpl_toolkits/axisartist/tests/baseline_images/test_axislines/axisline_style_tight.png and b/lib/mpl_toolkits/axisartist/tests/baseline_images/test_axislines/axisline_style_tight.png differ diff --git a/lib/mpl_toolkits/axisartist/tests/conftest.py b/lib/mpl_toolkits/axisartist/tests/conftest.py index 61c2de3e07ba..12eaac9ce2f4 100644 --- a/lib/mpl_toolkits/axisartist/tests/conftest.py +++ b/lib/mpl_toolkits/axisartist/tests/conftest.py @@ -1,2 +1,3 @@ -from matplotlib.testing.conftest import (mpl_test_settings, # noqa - pytest_configure, pytest_unconfigure) +from matplotlib.testing.conftest import ( # noqa + pytest_configure, pytest_unconfigure, + high_memory, mpl_test_settings) diff --git a/lib/mpl_toolkits/axisartist/tests/test_axis_artist.py b/lib/mpl_toolkits/axisartist/tests/test_axis_artist.py index d44a61b6dd4a..f366b9e96537 100644 --- a/lib/mpl_toolkits/axisartist/tests/test_axis_artist.py +++ b/lib/mpl_toolkits/axisartist/tests/test_axis_artist.py @@ -19,12 +19,13 @@ def test_ticks(): 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 = Ticks(ticksize=10, tick_direction="out", 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') +# TODO: tighten tolerance after baseline image is regenerated for text overhaul +@image_comparison(['axis_artist_labelbase.png'], style='default', tol=0.02) def test_labelbase(): # Remove this line when this test image is regenerated. plt.rcParams['text.kerning_factor'] = 6 @@ -41,7 +42,8 @@ def test_labelbase(): ax.add_artist(label) -@image_comparison(['axis_artist_ticklabels.png'], style='default') +# TODO: tighten tolerance after baseline image is regenerated for text overhaul +@image_comparison(['axis_artist_ticklabels.png'], style='default', tol=0.03) def test_ticklabels(): # Remove this line when this test image is regenerated. plt.rcParams['text.kerning_factor'] = 6 @@ -76,7 +78,8 @@ def test_ticklabels(): ax.set_ylim(0, 1) -@image_comparison(['axis_artist.png'], style='default') +# TODO: tighten tolerance after baseline image is regenerated for text overhaul +@image_comparison(['axis_artist.png'], style='default', tol=0.03) def test_axis_artist(): # Remove this line when this test image is regenerated. plt.rcParams['text.kerning_factor'] = 6 @@ -89,11 +92,11 @@ def test_axis_artist(): for loc in ('left', 'right', 'bottom'): helper = AxisArtistHelperRectlinear.Fixed(ax, loc=loc) axisline = AxisArtist(ax, helper, offset=None, axis_direction=loc) + axisline.major_ticks.set_tick_direction("in") 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 index b722316a5c0c..10c50c271ef2 100644 --- a/lib/mpl_toolkits/axisartist/tests/test_axislines.py +++ b/lib/mpl_toolkits/axisartist/tests/test_axislines.py @@ -7,10 +7,12 @@ from mpl_toolkits.axisartist import Axes, SubplotHost -@image_comparison(['SubplotZero.png'], style='default') +# TODO: tighten tolerance after baseline image is regenerated for text overhaul +@image_comparison(['SubplotZero.png'], style='default', tol=0.02) def test_SubplotZero(): # Remove this line when this test image is regenerated. - plt.rcParams['text.kerning_factor'] = 6 + plt.rcParams.update({ + "text.kerning_factor": 6, "xtick.direction": "in", "ytick.direction": "in"}) fig = plt.figure() @@ -28,10 +30,12 @@ def test_SubplotZero(): ax.set_ylabel("Test") -@image_comparison(['Subplot.png'], style='default') +# TODO: tighten tolerance after baseline image is regenerated for text overhaul +@image_comparison(['Subplot.png'], style='default', tol=0.02) def test_Subplot(): # Remove this line when this test image is regenerated. - plt.rcParams['text.kerning_factor'] = 6 + plt.rcParams.update({ + "text.kerning_factor": 6, "xtick.direction": "in", "ytick.direction": "in"}) fig = plt.figure() @@ -60,6 +64,8 @@ def test_Axes(): @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.update({"xtick.direction": "in", "ytick.direction": "in"}) data = np.ones((6, 6)) data[2, 2] = 2 data[0, :] = 0 @@ -83,14 +89,16 @@ def test_ParasiteAxesAuxTrans(): 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)) + 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(): + # Remove this line when this test image is regenerated. + plt.rcParams.update({"xtick.direction": "in", "ytick.direction": "in"}) fig = plt.figure(figsize=(2, 2)) ax = fig.add_subplot(axes_class=AxesZero) ax.axis["xzero"].set_axisline_style("-|>") @@ -105,6 +113,8 @@ def test_axisline_style(): @image_comparison(['axisline_style_size_color.png'], remove_text=True, style='mpl20') def test_axisline_style_size_color(): + # Remove this line when this test image is regenerated. + plt.rcParams.update({"xtick.direction": "in", "ytick.direction": "in"}) fig = plt.figure(figsize=(2, 2)) ax = fig.add_subplot(axes_class=AxesZero) ax.axis["xzero"].set_axisline_style("-|>", size=2.0, facecolor='r') @@ -119,7 +129,9 @@ def test_axisline_style_size_color(): @image_comparison(['axisline_style_tight.png'], remove_text=True, style='mpl20') def test_axisline_style_tight(): - fig = plt.figure(figsize=(2, 2)) + # Remove this line when this test image is regenerated. + plt.rcParams.update({"xtick.direction": "in", "ytick.direction": "in"}) + 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) @@ -129,11 +141,12 @@ def test_axisline_style_tight(): for direction in ("left", "right", "bottom", "top"): ax.axis[direction].set_visible(False) - fig.tight_layout() - -@image_comparison(['subplotzero_ylabel.png'], style='mpl20') +# TODO: tighten tolerance after baseline image is regenerated for text overhaul +@image_comparison(['subplotzero_ylabel.png'], style='mpl20', tol=0.02) def test_subplotzero_ylabel(): + # Remove this line when this test image is regenerated. + plt.rcParams.update({"xtick.direction": "in", "ytick.direction": "in"}) fig = plt.figure() ax = fig.add_subplot(111, axes_class=SubplotZero) diff --git a/lib/mpl_toolkits/axisartist/tests/test_floating_axes.py b/lib/mpl_toolkits/axisartist/tests/test_floating_axes.py index 7644fea16965..98d49dc0cf37 100644 --- a/lib/mpl_toolkits/axisartist/tests/test_floating_axes.py +++ b/lib/mpl_toolkits/axisartist/tests/test_floating_axes.py @@ -1,5 +1,7 @@ import numpy as np +import pytest + import matplotlib.pyplot as plt import matplotlib.projections as mprojections import matplotlib.transforms as mtransforms @@ -21,10 +23,12 @@ def test_subplot(): # remove when image is regenerated. @image_comparison(['curvelinear3.png'], style='default', tol=5) def test_curvelinear3(): + # Remove this line when this test image is regenerated. + plt.rcParams.update({"xtick.direction": "in", "ytick.direction": "in"}) fig = plt.figure(figsize=(5, 5)) tr = (mtransforms.Affine2D().scale(np.pi / 180, 1) + - mprojections.PolarAxes.PolarTransform(apply_theta_transforms=False)) + mprojections.PolarAxes.PolarTransform()) grid_helper = GridHelperCurveLinear( tr, extremes=(0, 360, 10, 3), @@ -68,12 +72,13 @@ def test_curvelinear3(): @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 + plt.rcParams.update({ + "text.kerning_factor": 6, "xtick.direction": "in", "ytick.direction": "in"}) fig = plt.figure(figsize=(5, 5)) tr = (mtransforms.Affine2D().scale(np.pi / 180, 1) + - mprojections.PolarAxes.PolarTransform(apply_theta_transforms=False)) + mprojections.PolarAxes.PolarTransform()) grid_helper = GridHelperCurveLinear( tr, extremes=(120, 30, 10, 0), @@ -113,3 +118,29 @@ def test_axis_direction(): 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_helper_curvelinear.py b/lib/mpl_toolkits/axisartist/tests/test_grid_helper_curvelinear.py index 1b266044bdd0..e59e31fe3c88 100644 --- a/lib/mpl_toolkits/axisartist/tests/test_grid_helper_curvelinear.py +++ b/lib/mpl_toolkits/axisartist/tests/test_grid_helper_curvelinear.py @@ -17,6 +17,9 @@ @image_comparison(['custom_transform.png'], style='default', tol=0.2) def test_custom_transform(): + # Remove this line when this test image is regenerated. + plt.rcParams.update({"xtick.direction": "in", "ytick.direction": "in"}) + class MyTransform(Transform): input_dims = output_dims = 2 @@ -76,14 +79,16 @@ def inverted(self): ax1.grid(True) -@image_comparison(['polar_box.png'], style='default', tol=0.04) +# TODO: tighten tolerance after baseline image is regenerated for text overhaul +@image_comparison(['polar_box.png'], style='default', tol=0.09) def test_polar_box(): + # Remove this line when this test image is regenerated. + plt.rcParams.update({"xtick.direction": "in", "ytick.direction": "in"}) 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(apply_theta_transforms=False)) + 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 @@ -137,16 +142,17 @@ def test_polar_box(): # Remove tol & kerning_factor when this test image is regenerated. -@image_comparison(['axis_direction.png'], style='default', tol=0.13) +@image_comparison(['axis_direction.png'], style='default', tol=0.15) def test_axis_direction(): - plt.rcParams['text.kerning_factor'] = 6 + # Remove this line when this test image is regenerated. + plt.rcParams.update({ + "text.kerning_factor": 6, "xtick.direction": "in", "ytick.direction": "in"}) 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(apply_theta_transforms=False)) + 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 diff --git a/lib/mpl_toolkits/mplot3d/art3d.py b/lib/mpl_toolkits/mplot3d/art3d.py index 0467d2e96e5e..6898a8aaf4cf 100644 --- a/lib/mpl_toolkits/mplot3d/art3d.py +++ b/lib/mpl_toolkits/mplot3d/art3d.py @@ -15,10 +15,9 @@ from matplotlib import ( _api, artist, cbook, colors as mcolors, lines, text as mtext, - path as mpath) + path as mpath, rcParams) from matplotlib.collections import ( Collection, LineCollection, PolyCollection, PatchCollection, PathCollection) -from matplotlib.colors import Normalize from matplotlib.patches import Patch from . import proj3d @@ -59,11 +58,11 @@ def get_dir_vector(zdir): x, y, z : array The direction vector. """ - if zdir == 'x': + if cbook._str_equal(zdir, 'x'): return np.array((1, 0, 0)) - elif zdir == 'y': + elif cbook._str_equal(zdir, 'y'): return np.array((0, 1, 0)) - elif zdir == 'z': + elif cbook._str_equal(zdir, 'z'): return np.array((0, 0, 1)) elif zdir is None: return np.array((0, 0, 0)) @@ -75,19 +74,19 @@ def get_dir_vector(zdir): def _viewlim_mask(xs, ys, zs, axes): """ - Return original points with points outside the axes view limits masked. + Return the mask of the points outside the axes view limits. Parameters ---------- xs, ys, zs : array-like - The points to mask. + The points to mask. These should be in data coordinates. axes : Axes3D The axes to use for the view limits. Returns ------- - xs_masked, ys_masked, zs_masked : np.ma.array - The masked points. + 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, @@ -95,10 +94,7 @@ def _viewlim_mask(xs, ys, zs, axes): ys > axes.xy_viewLim.ymax, zs < axes.zz_viewLim.xmin, zs > axes.zz_viewLim.xmax)) - xs_masked = np.ma.array(xs, mask=mask) - ys_masked = np.ma.array(ys, mask=mask) - zs_masked = np.ma.array(zs, mask=mask) - return xs_masked, ys_masked, zs_masked + return mask class Text3D(mtext.Text): @@ -117,6 +113,8 @@ class Text3D(mtext.Text): axlim_clip : bool, default: False Whether to hide text outside the axes view limits. + .. versionadded:: 3.10 + Other Parameters ---------------- **kwargs @@ -125,6 +123,16 @@ class Text3D(mtext.Text): def __init__(self, x=0, y=0, z=0, text='', zdir='z', axlim_clip=False, **kwargs): + if 'rotation' in kwargs: + _api.warn_external( + "The `rotation` parameter has not yet been implemented " + "and is currently ignored." + ) + if 'rotation_mode' in kwargs: + _api.warn_external( + "The `rotation_mode` parameter has not yet been implemented " + "and is currently ignored." + ) mtext.Text.__init__(self, x, y, text, **kwargs) self.set_3d_properties(z, zdir, axlim_clip) @@ -173,6 +181,8 @@ def set_3d_properties(self, z=0, zdir='z', axlim_clip=False): 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) @@ -182,14 +192,16 @@ def set_3d_properties(self, z=0, zdir='z', axlim_clip=False): @artist.allow_rasterization def draw(self, renderer): if self._axlim_clip: - xs, ys, zs = _viewlim_mask(self._x, self._y, self._z, self.axes) - position3d = np.ma.row_stack((xs, ys, zs)).ravel().filled(np.nan) + 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: - xs, ys, zs = self._x, self._y, self._z - position3d = np.asanyarray([xs, ys, zs]) + pos3d = np.array([self._x, self._y, self._z], dtype=float) - proj = proj3d._proj_trans_points( - [position3d, position3d + self._dir_vec], self.axes.M) + dir_end = pos3d + self._dir_vec + points = np.asarray([pos3d, dir_end]) + proj = proj3d._scale_proj_transform( + points[:, 0], points[:, 1], points[:, 2], self.axes) dx = proj[0][1] - proj[0][0] dy = proj[1][1] - proj[1][0] angle = math.degrees(math.atan2(dy, dx)) @@ -217,6 +229,8 @@ def text_2d_to_3d(obj, z=0, zdir='z', axlim_clip=False): 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, axlim_clip) @@ -265,6 +279,8 @@ def set_3d_properties(self, zs=0, zdir='z', axlim_clip=False): 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() @@ -313,12 +329,15 @@ def get_data_3d(self): @artist.allow_rasterization def draw(self, renderer): if self._axlim_clip: - xs3d, ys3d, zs3d = _viewlim_mask(*self._verts3d, self.axes) + 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) + xs, ys, zs, tis = proj3d._scale_proj_transform_clip(xs3d, ys3d, zs3d, self.axes) self.set_data(xs, ys) super().draw(renderer) self.stale = False @@ -337,6 +356,8 @@ def line_2d_to_3d(line, zs=0, zdir='z', axlim_clip=False): 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 @@ -404,9 +425,11 @@ 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.row_stack(_viewlim_mask(*vs.T, self.axes)).T + 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] + xyzs_list = [proj3d._scale_proj_transform( + vs[:, 0], vs[:, 1], vs[:, 2], self.axes) 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]) @@ -433,6 +456,30 @@ class Line3DCollection(LineCollection): 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.""" @@ -450,22 +497,38 @@ def do_3d_projection(self): """ Project the points according to renderer matrix. """ - segments = self._segments3d + segments = np.asanyarray(self._segments3d) + + # Handle empty segments + if segments.size == 0: + LineCollection.set_segments(self, []) + return np.nan + + mask = False + if np.ma.isMA(segments): + mask = segments.mask + if self._axlim_clip: - all_points = np.ma.vstack(segments) - masked_points = np.ma.column_stack([*_viewlim_mask(*all_points.T, - self.axes)]) - segment_lengths = [np.shape(segment)[0] for segment in segments] - segments = np.split(masked_points, np.cumsum(segment_lengths[:-1])) - xyslist = [proj3d._proj_trans_points(points, self.axes.M) - for points in segments] - segments_2d = [np.ma.column_stack([xs, ys]) for xs, ys, zs in xyslist] + 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._scale_proj_transform_vectors(segments, self.axes), 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 @@ -495,6 +558,8 @@ def __init__(self, *args, zs=(), zdir='z', axlim_clip=False, **kwargs): 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, axlim_clip) @@ -514,6 +579,8 @@ def set_3d_properties(self, verts, zs=0, zdir='z', axlim_clip=False): 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) @@ -531,12 +598,12 @@ def get_path(self): def do_3d_projection(self): s = self._segment3d if self._axlim_clip: - xs, ys, zs = _viewlim_mask(*zip(*s), self.axes) + 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) + vxs, vys, vzs, vis = proj3d._scale_proj_transform_clip(xs, ys, zs, self.axes) self._path2d = mpath.Path(np.ma.column_stack([vxs, vys])) return min(vzs) @@ -559,6 +626,8 @@ def __init__(self, path, *, zs=(), zdir='z', axlim_clip=False, **kwargs): 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) @@ -579,6 +648,8 @@ def set_3d_properties(self, path, zs=0, zdir='z', axlim_clip=False): 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) @@ -587,12 +658,12 @@ def set_3d_properties(self, path, zs=0, zdir='z', axlim_clip=False): def do_3d_projection(self): s = self._segment3d if self._axlim_clip: - xs, ys, zs = _viewlim_mask(*zip(*s), self.axes) + 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) + vxs, vys, vzs, vis = proj3d._scale_proj_transform_clip(xs, ys, zs, self.axes) self._path2d = mpath.Path(np.ma.column_stack([vxs, vys]), self._code3d) return min(vzs) @@ -627,8 +698,16 @@ class Patch3DCollection(PatchCollection): A collection of 3D patches. """ - def __init__(self, *args, - zs=0, zdir='z', depthshade=True, axlim_clip=False, **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* @@ -639,18 +718,31 @@ def __init__(self, *args, :class:`~matplotlib.collections.PatchCollection`. In addition, keywords *zs=0* and *zdir='z'* are available. - Also, the keyword argument *depthshade* is available to indicate - whether to shade the patches in order to give the appearance of depth - (default is *True*). This is typically desired in scatter plots. + 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, axlim_clip) def get_depthshade(self): return self._depthshade - def set_depthshade(self, depthshade): + def set_depthshade( + self, + depthshade, + depthshade_minalpha=None, + ): """ Set whether depth shading is performed on collection members. @@ -659,8 +751,15 @@ def set_depthshade(self, depthshade): depthshade : bool Whether to shade the patches in order to give the appearance of depth. + depthshade_minalpha : float, default: :rc:`axes3d.depthshade_minalpha` + 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 set_sort_zpos(self, val): @@ -683,6 +782,8 @@ def set_3d_properties(self, zs, zdir, axlim_clip=False): 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. @@ -701,14 +802,16 @@ def set_3d_properties(self, zs, zdir, axlim_clip=False): def do_3d_projection(self): if self._axlim_clip: - xs, ys, zs = _viewlim_mask(*self._offsets3d, self.axes) + 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) + vxs, vys, vzs, vis = proj3d._scale_proj_transform_clip(xs, ys, zs, self.axes) self._vzs = vzs - super().set_offsets(np.ma.column_stack([vxs, vys])) + 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) @@ -717,7 +820,11 @@ def do_3d_projection(self): def _maybe_depth_shade_and_sort_colors(self, color_array): color_array = ( - _zalpha(color_array, self._vzs) + _zalpha( + color_array, + self._vzs, + min_alpha=self._depthshade_minalpha, + ) if self._vzs is not None and self._depthshade else color_array ) @@ -737,13 +844,44 @@ def get_edgecolor(self): 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, axlim_clip=False, **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* @@ -754,11 +892,20 @@ def __init__(self, *args, :class:`~matplotlib.collections.PathCollection`. In addition, keywords *zs=0* and *zdir='z'* are available. - Also, the keyword argument *depthshade* is available to indicate - whether to shade the patches in order to give the appearance of depth - (default is *True*). This is typically desired in scatter plots. + 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, axlim_clip) @@ -789,6 +936,8 @@ def set_3d_properties(self, zs, zdir, axlim_clip=False): 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. @@ -837,7 +986,11 @@ def set_linewidth(self, lw): def get_depthshade(self): return self._depthshade - def set_depthshade(self, depthshade): + def set_depthshade( + self, + depthshade, + depthshade_minalpha=None, + ): """ Set whether depth shading is performed on collection members. @@ -846,18 +999,31 @@ def set_depthshade(self, depthshade): 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: - xs, ys, zs = _viewlim_mask(*self._offsets3d, self.axes) + 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: - xs, ys, zs = self._offsets3d - vxs, vys, vzs, vis = proj3d._proj_transform_clip(xs, ys, zs, - self.axes.M, - self.axes._focal_length) + xyzs = self._offsets3d + vxs, vys, vzs, vis = proj3d._scale_proj_transform_clip(*xyzs, self.axes) + 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 @@ -902,14 +1068,22 @@ def _use_zordered_offset(self): self._offsets = old_offset def _maybe_depth_shade_and_sort_colors(self, color_array): - color_array = ( - _zalpha(color_array, self._vzs) - if self._vzs is not None and self._depthshade - else 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, self._alpha) + + return mcolors.to_rgba_array(color_array) def get_facecolor(self): return self._maybe_depth_shade_and_sort_colors(super().get_facecolor()) @@ -923,7 +1097,15 @@ def get_edgecolor(self): return self._maybe_depth_shade_and_sort_colors(super().get_edgecolor()) -def patch_collection_2d_to_3d(col, zs=0, zdir='z', depthshade=True, axlim_clip=False): +def patch_collection_2d_to_3d( + col, + zs=0, + zdir="z", + depthshade=None, + axlim_clip=False, + *args, + depthshade_minalpha=None, +): """ Convert a `.PatchCollection` into a `.Patch3DCollection` object (or a `.PathCollection` into a `.Path3DCollection` object). @@ -939,17 +1121,29 @@ def patch_collection_2d_to_3d(col, zs=0, zdir='z', depthshade=True, axlim_clip=F zdir : {'x', 'y', 'z'} The axis in which to place the patches. Default: "z". See `.get_dir_vector` for a description of the values. - depthshade : bool, default: True + depthshade : bool, default: :rc:`axes3d.depthshade` Whether to shade the patches to give a sense of depth. axlim_clip : bool, default: False Whether to hide patches with a vertex outside the axes view limits. + + .. versionadded:: 3.10 + + depthshade_minalpha : float, default: :rc:`axes3d.depthshade_minalpha` + Sets the minimum alpha value used by depth-shading. + + .. 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._depthshade_minalpha = depthshade_minalpha col._in_draw = False col.set_3d_properties(zs, zdir, axlim_clip) @@ -1001,6 +1195,8 @@ def __init__(self, verts, *args, zsort='average', shade=False, 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`. @@ -1062,16 +1258,35 @@ def get_vector(self, segments3d): return self._get_vector(segments3d) def _get_vector(self, segments3d): - """Optimize points for projection.""" - if len(segments3d): - xs, ys, zs = np.vstack(segments3d).T - else: # vstack can't stack zero arrays. - xs, ys, zs = [], [], [] - ones = np.ones(len(xs)) - self._vec = np.array([xs, ys, zs, ones]) + """ + Optimize points for projection. - indices = [0, *np.cumsum([len(segment) for segment in segments3d])] - self._segslices = [*map(slice, indices[:-1], indices[1:])] + 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): """ @@ -1133,52 +1348,73 @@ def do_3d_projection(self): 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._scale_proj_transform_vectors(self._faces, self.axes) + if self._axlim_clip: - xs, ys, zs = _viewlim_mask(*self._vec[0:3], self.axes) - if self._vec.shape[0] == 4: # Will be 3 (xyz) or 4 (xyzw) - w_masked = np.ma.masked_where(zs.mask, self._vec[3]) - vec = np.ma.array([xs, ys, zs, w_masked]) - else: - vec = np.ma.array([xs, ys, zs]) - else: - vec = self._vec - txs, tys, tzs = proj3d._proj_transform_vec(vec, self.axes.M) - xyzlist = [(txs[sl], tys[sl], tzs[sl]) for sl in self._segslices] + 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._facecolor3d cedge = self._edgecolor3d - if len(cface) != len(xyzlist): - cface = cface.repeat(len(xyzlist), axis=0) - if len(cedge) != len(xyzlist): + 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) - - if xyzlist: - # sort by depth (furthest drawn first) - z_segments_2d = sorted( - ((self._zsortfunc(zs.data), np.ma.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) - - _, segments_2d, self._facecolors2d, self._edgecolors2d, idxs = \ - zip(*z_segments_2d) - else: - segments_2d = [] - self._facecolors2d = np.empty((0, 4)) - self._edgecolors2d = np.empty((0, 4)) - idxs = [] - - if self._codes3d is not None: - codes = [self._codes3d[idx] for idx in idxs] - PolyCollection.set_verts_and_codes(self, segments_2d, codes) + cedge = cedge.repeat(num_faces, axis=0) + + 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._edgecolor3d) != len(cface): + 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 @@ -1186,11 +1422,11 @@ def do_3d_projection(self): zvec = np.array([[0], [0], [self._sort_zpos], [1]]) 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 @@ -1290,17 +1526,31 @@ def rotate_axes(xs, ys, zs, zdir): return xs, ys, zs -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. +def _zalpha( + colors, + zs, + min_alpha=0.3, + _data_scale=None, +): + """Modify the alpha values of the color list according to z-depth.""" + 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]) @@ -1382,6 +1632,7 @@ def _generate_normals(polygons): 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, :] diff --git a/lib/mpl_toolkits/mplot3d/axes3d.py b/lib/mpl_toolkits/mplot3d/axes3d.py index d0ba360c314b..7d0a5ae009c4 100644 --- a/lib/mpl_toolkits/mplot3d/axes3d.py +++ b/lib/mpl_toolkits/mplot3d/axes3d.py @@ -8,9 +8,30 @@ Module containing Axes3D, an object which can plot 3D objects on a 2D matplotlib figure. + +Coordinate Systems +------------------ +3D plotting involves several coordinate transformations: + +1. **Data coordinates**: The user's raw x, y, z values. + +2. **Transformed coordinates**: Data coordinates after applying axis scale + transforms (log, symlog, etc.). For linear scales, these equal data + coordinates. Zoom/pan operations work in this space to ensure uniform + behavior with non-linear scales. + +3. **Normalized coordinates**: Transformed coordinates mapped to a [0, 1] + unit cube based on the current axis limits. + +4. **Projected coordinates**: 2D coordinates after applying the 3D to 2D + projection matrix, ready for display. + +Artists receive data in data coordinates, apply scale transforms internally +via ``do_3d_projection()``, then project to 2D for rendering. """ from collections import defaultdict +from functools import partialmethod import itertools import math import textwrap @@ -70,7 +91,7 @@ def __init__( ---------- fig : Figure The parent figure. - rect : tuple (left, bottom, width, height), default: None. + rect : tuple (left, bottom, width, height), default: (0, 0, 1, 1) The ``(left, bottom, width, height)`` Axes position. elev : float, default: 30 The elevation angle in degrees rotates the camera above and below @@ -106,12 +127,13 @@ def __init__( 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. + along the view direction for supported artist types (currently + Collections and Patches only). 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 + order 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 @@ -232,8 +254,16 @@ def get_zaxis(self): get_zticklines = _axis_method_wrapper("zaxis", "get_ticklines") def _transformed_cube(self, vals): - """Return cube with limits from *vals* transformed by self.M.""" + """Return cube with limits from *vals* transformed by self.M. + + The vals are in data space and are first transformed through the + axis scale transforms before being projected. + """ minx, maxx, miny, maxy, minz, maxz = vals + # Transform from data space to transformed coordinates + minx, maxx = self.xaxis.get_transform().transform([minx, maxx]) + miny, maxy = self.yaxis.get_transform().transform([miny, maxy]) + minz, maxz = self.zaxis.get_transform().transform([minz, maxz]) xyzs = [(minx, miny, minz), (maxx, miny, minz), (maxx, maxy, minz), @@ -242,8 +272,25 @@ def _transformed_cube(self, vals): (maxx, miny, maxz), (maxx, maxy, maxz), (minx, maxy, maxz)] - return proj3d._proj_points(xyzs, self.M) + return np.column_stack(proj3d._proj_trans_points(xyzs, self.M)) + + def _update_transScale(self): + """ + Override transScale to always use identity transforms. + In 2D axes, transScale applies scale transforms (log, symlog, etc.) to + convert data coordinates to display coordinates. In 3D axes, scale + transforms are applied to data coordinates before 3D projection via + each axis's transform. The projected 2D coordinates are already in + display space, so transScale must be identity to avoid double-scaling. + """ + self.transScale.set( + mtransforms.blended_transform_factory( + mtransforms.IdentityTransform(), + mtransforms.IdentityTransform())) + + @_api.delete_parameter("3.11", "share") + @_api.delete_parameter("3.11", "anchor") def set_aspect(self, aspect, adjustable=None, anchor=None, share=False): """ Set the aspect ratios. @@ -263,39 +310,31 @@ def set_aspect(self, aspect, adjustable=None, anchor=None, share=False): 'equalyz' adapt the y and z axes to have equal aspect ratios. ========= ================================================== - 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. + adjustable : {'box', 'datalim'}, default: 'box' + Defines which parameter to adjust to meet the aspect ratio. + + - 'box': Change the physical dimensions of the axes bounding box. + - 'datalim': Change the x, y, or z data limits. 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 - specify the anchor are abbreviations of cardinal directions: - - ===== ===================== - value description - ===== ===================== - 'C' centered - 'SW' lower left corner - 'S' middle of bottom edge - 'SE' lower right corner - etc. - ===== ===================== - - See `~.Axes.set_anchor` for further details. + .. deprecated:: 3.11 + This parameter has no effect. share : bool, default: False - If ``True``, apply the settings to all shared Axes. + .. deprecated:: 3.11 + This parameter has no effect. See Also -------- mpl_toolkits.mplot3d.axes3d.Axes3D.set_box_aspect """ + if adjustable is None: + adjustable = 'box' + _api.check_in_list(['box', 'datalim'], adjustable=adjustable) _api.check_in_list(('auto', 'equal', 'equalxy', 'equalyz', 'equalxz'), aspect=aspect) - super().set_aspect( - aspect='auto', adjustable=adjustable, anchor=anchor, share=share) + + self.set_adjustable(adjustable) self._aspect = aspect if aspect in ('equal', 'equalxy', 'equalxz', 'equalyz'): @@ -608,6 +647,42 @@ def auto_scale_xyz(self, X, Y, Z=None, had_data=None): # Let autoscale_view figure out how to use this data. self.autoscale_view() + def _autoscale_axis(self, axis, v0, v1, minpos, margin, set_bound, _tight): + """ + Autoscale a single axis. + + Parameters + ---------- + axis : Axis + The axis to autoscale. + v0, v1 : float + Data interval limits. + minpos : float + Minimum positive value for log-scale handling. + margin : float + Margin to apply (e.g., self._xmargin). + set_bound : callable + Function to set the axis bound (e.g., self.set_xbound). + _tight : bool + Whether to use tight bounds. + """ + locator = axis.get_major_locator() + v0, v1 = locator.nonsingular(v0, v1) + # Validate limits for the scale (e.g., positive for log scale) + v0, v1 = axis._scale.limit_range_for_scale(v0, v1, minpos) + if margin > 0: + # Apply margin in transformed space to handle non-linear scales + transform = axis.get_transform() + inverse_trans = transform.inverted() + v0t, v1t = transform.transform([v0, v1]) + delta = (v1t - v0t) * margin + if not np.isfinite(delta): + delta = 0 + v0, v1 = inverse_trans.transform([v0t - delta, v1t + delta]) + if not _tight: + v0, v1 = locator.view_limits(v0, v1) + set_bound(v0, v1, self._view_margin) + def autoscale_view(self, tight=None, scalex=True, scaley=True, scalez=True): """ @@ -632,40 +707,22 @@ def autoscale_view(self, tight=None, _tight = self._tight = bool(tight) 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) - if self._xmargin > 0: - delta = (x1 - x0) * self._xmargin - x0 -= delta - x1 += delta - if not _tight: - x0, x1 = xlocator.view_limits(x0, x1) - self.set_xbound(x0, x1, self._view_margin) + self._autoscale_axis( + self.xaxis, *self.xy_dataLim.intervalx, + self.xy_dataLim.minposx, self._xmargin, + self.set_xbound, _tight) 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) - if self._ymargin > 0: - delta = (y1 - y0) * self._ymargin - y0 -= delta - y1 += delta - if not _tight: - y0, y1 = ylocator.view_limits(y0, y1) - self.set_ybound(y0, y1, self._view_margin) + self._autoscale_axis( + self.yaxis, *self.xy_dataLim.intervaly, + self.xy_dataLim.minposy, self._ymargin, + self.set_ybound, _tight) 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) - if self._zmargin > 0: - delta = (z1 - z0) * self._zmargin - z0 -= delta - z1 += delta - if not _tight: - z0, z1 = zlocator.view_limits(z0, z1) - self.set_zbound(z0, z1, self._view_margin) + self._autoscale_axis( + self.zaxis, *self.zz_dataLim.intervalx, + self.zz_dataLim.minposx, self._zmargin, + self.set_zbound, _tight) def get_w_lims(self): """Get 3D world limits.""" @@ -766,7 +823,8 @@ def set_zbound(self, lower=None, upper=None, view_margin=None): lower, upper, view_margin) def _set_lim3d(self, axis, lower=None, upper=None, *, emit=True, - auto=False, view_margin=None, axmin=None, axmax=None): + auto=False, view_margin=None, axmin=None, axmax=None, + minpos=np.inf): """ Set 3D axis limits. """ @@ -792,9 +850,20 @@ def _set_lim3d(self, axis, lower=None, upper=None, *, emit=True, view_margin = self._view_margin else: view_margin = 0 - delta = (upper - lower) * view_margin - lower -= delta - upper += delta + # Apply margin in transformed space to handle non-linear scales properly + if view_margin > 0 and hasattr(axis, '_scale') and axis._scale is not None: + transform = axis.get_transform() + inverse_trans = transform.inverted() + lower, upper = axis._scale.limit_range_for_scale(lower, upper, minpos) + lower_t, upper_t = transform.transform([lower, upper]) + delta = (upper_t - lower_t) * view_margin + if np.isfinite(delta): + new_range = [lower_t - delta, upper_t + delta] + lower, upper = inverse_trans.transform(new_range) + else: + 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, @@ -867,7 +936,8 @@ def set_xlim(self, left=None, right=None, *, emit=True, auto=False, >>> set_xlim(5000, 0) """ return self._set_lim3d(self.xaxis, left, right, emit=emit, auto=auto, - view_margin=view_margin, axmin=xmin, axmax=xmax) + view_margin=view_margin, axmin=xmin, axmax=xmax, + minpos=self.xy_dataLim.minposx) def set_ylim(self, bottom=None, top=None, *, emit=True, auto=False, view_margin=None, ymin=None, ymax=None): @@ -939,7 +1009,8 @@ def set_ylim(self, bottom=None, top=None, *, emit=True, auto=False, >>> set_ylim(5000, 0) """ return self._set_lim3d(self.yaxis, bottom, top, emit=emit, auto=auto, - view_margin=view_margin, axmin=ymin, axmax=ymax) + view_margin=view_margin, axmin=ymin, axmax=ymax, + minpos=self.xy_dataLim.minposy) def set_zlim(self, bottom=None, top=None, *, emit=True, auto=False, view_margin=None, zmin=None, zmax=None): @@ -1011,7 +1082,8 @@ def set_zlim(self, bottom=None, top=None, *, emit=True, auto=False, >>> set_zlim(5000, 0) """ return self._set_lim3d(self.zaxis, bottom, top, emit=emit, auto=auto, - view_margin=view_margin, axmin=zmin, axmax=zmax) + view_margin=view_margin, axmin=zmin, axmax=zmax, + minpos=self.zz_dataLim.minposx) set_xlim3d = set_xlim set_ylim3d = set_ylim @@ -1049,25 +1121,92 @@ def get_zlim(self): 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( + # Custom scale setters that handle limit validation for non-linear scales + def _set_axis_scale(self, axis, value, **kwargs): """ - Set the {}-axis scale. + Set scale for an axis and constrain limits to valid range. Parameters ---------- - value : {{"linear"}} - The axis scale type to apply. 3D Axes currently only support - linear scales; other scales yield nonsensical results. + axis : Axis + The axis to set the scale on. + value : str + The scale name. + **kwargs + Forwarded to scale constructor. + """ + # For non-linear scales on the z-axis, switch from the [0, 1] + + # margin=0 representation to the same xymargin + margin=0.05 + # representation that x/y use. Both produce identical linear limits, + # but only the xymargin form has valid positive lower bounds for log + # etc. This must happen before _set_axes_scale because that triggers + # autoscale_view internally. + if (axis is self.zaxis and value != 'linear' + and np.array_equal(self.zz_dataLim.get_points(), [[0, 0], [1, 1]])): + xymargin = 0.05 * 10/11 + self.zz_dataLim = Bbox([[xymargin, xymargin], + [1 - xymargin, 1 - xymargin]]) + self._zmargin = self._xmargin + axis._set_axes_scale(value, **kwargs) + + def set_xscale(self, value, **kwargs): + """ + Set the x-axis scale. + + Parameters + ---------- + value : {"linear", "log", "symlog", "logit", ...} + The axis scale type to apply. See `~.scale.ScaleBase` for + the list of available scales. + + **kwargs + Keyword arguments are forwarded to the scale class. + For example, ``base=2`` can be passed when using a log scale. + """ + self._set_axis_scale(self.xaxis, value, **kwargs) + + def set_yscale(self, value, **kwargs): + """ + Set the y-axis scale. + + Parameters + ---------- + value : {"linear", "log", "symlog", "logit", ...} + The axis scale type to apply. See `~.scale.ScaleBase` for + the list of available scales. + + **kwargs + Keyword arguments are forwarded to the scale class. + For example, ``base=2`` can be passed when using a log scale. + """ + self._set_axis_scale(self.yaxis, value, **kwargs) + + def set_zscale(self, value, **kwargs): + """ + Set the z-axis scale. + + Parameters + ---------- + value : {"linear", "log", "symlog", "logit", ...} + The axis scale type to apply. See `~.scale.ScaleBase` for + the list of available scales. **kwargs - Keyword arguments are nominally forwarded to the scale class, but - none of them is applicable for linear scales. - """.format, - ["x", "y", "z"]) + Keyword arguments are forwarded to the scale class. + For example, ``base=2`` can be passed when using a log scale. + """ + self._set_axis_scale(self.zaxis, value, **kwargs) + + def _raise_semilog_not_implemented(self, name, *args, **kwargs): + raise NotImplementedError( + f"Axes3D does not support {name}. Use ax.set_xscale/set_yscale/set_zscale " + "and ax.plot(...) instead." + ) + + semilogx = partialmethod(_raise_semilog_not_implemented, "semilogx") + semilogy = partialmethod(_raise_semilog_not_implemented, "semilogy") + semilogz = partialmethod(_raise_semilog_not_implemented, "semilogz") + loglog = partialmethod(_raise_semilog_not_implemented, "loglog") get_zticks = _axis_method_wrapper("zaxis", "get_ticklocs") set_zticks = _axis_method_wrapper("zaxis", "set_ticks") @@ -1152,7 +1291,7 @@ def view_init(self, elev=None, azim=None, roll=None, vertical_axis="z", azim = self.initial_azim if roll is None: roll = self.initial_roll - vertical_axis = _api.check_getitem( + vertical_axis = _api.getitem_checked( {name: idx for idx, name in enumerate(self._axis_names)}, vertical_axis=vertical_axis, ) @@ -1215,17 +1354,81 @@ def _roll_to_vertical( else: return np.roll(arr, (self._vertical_axis - 2)) + def _get_scaled_limits(self): + """ + Get axis limits transformed through their respective scale transforms. + + Returns + ------- + tuple + (xmin_scaled, xmax_scaled, ymin_scaled, ymax_scaled, + zmin_scaled, zmax_scaled) + """ + xmin, xmax = self.xaxis.get_transform().transform(self.get_xlim3d()) + ymin, ymax = self.yaxis.get_transform().transform(self.get_ylim3d()) + zmin, zmax = self.zaxis.get_transform().transform(self.get_zlim3d()) + return xmin, xmax, ymin, ymax, zmin, zmax + + def _untransform_point(self, x, y, z): + """ + Convert a point from transformed coordinates to data coordinates. + + Parameters + ---------- + x, y, z : float + A single point in transformed coordinates. + + Returns + ------- + x_data, y_data, z_data : float + The point in data coordinates. + """ + x_data = self.xaxis.get_transform().inverted().transform([x])[0] + y_data = self.yaxis.get_transform().inverted().transform([y])[0] + z_data = self.zaxis.get_transform().inverted().transform([z])[0] + return x_data, y_data, z_data + + def _set_lims_from_transformed(self, xmin_t, xmax_t, ymin_t, ymax_t, + zmin_t, zmax_t): + """ + Set axis limits from transformed coordinates. + + Converts limits from transformed coordinates back to data coordinates, + applies limit_range_for_scale validation, and sets the axis limits. + + Parameters + ---------- + xmin_t, xmax_t, ymin_t, ymax_t, zmin_t, zmax_t : float + Axis limits in transformed coordinates. + """ + # Transform back to data space + xmin, xmax = self.xaxis.get_transform().inverted().transform([xmin_t, xmax_t]) + ymin, ymax = self.yaxis.get_transform().inverted().transform([ymin_t, ymax_t]) + zmin, zmax = self.zaxis.get_transform().inverted().transform([zmin_t, zmax_t]) + + # Validate limits for scale constraints (e.g., positive for log scale) + xmin, xmax = self.xaxis._scale.limit_range_for_scale( + xmin, xmax, self.xy_dataLim.minposx) + ymin, ymax = self.yaxis._scale.limit_range_for_scale( + ymin, ymax, self.xy_dataLim.minposy) + zmin, zmax = self.zaxis._scale.limit_range_for_scale( + zmin, zmax, self.zz_dataLim.minposx) + + # Set the new axis limits + self.set_xlim3d(xmin, xmax, auto=None) + self.set_ylim3d(ymin, ymax, auto=None) + self.set_zlim3d(zmin, zmax, auto=None) + def get_proj(self): """Create the projection matrix from the current viewing position.""" # 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, - ) + # For non-linear scales, we use the scaled limits so the world + # transformation maps transformed coordinates (not data coordinates) + # to the unit cube + scaled_limits = self._get_scaled_limits() + worldM = proj3d.world_transformation(*scaled_limits, pb_aspect=box_aspect) # Look into the middle of the world coordinates: R = 0.5 * box_aspect @@ -1400,7 +1603,7 @@ def _set_view(self, view): 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: @@ -1458,7 +1661,7 @@ def _location_coords(self, xv, yv, renderer): def _get_camera_loc(self): """ - Returns the current camera location in data coordinates. + Returns the current camera location in transformed coordinates. """ cx, cy, cz, dx, dy, dz = self._get_w_centers_ranges() c = np.array([cx, cy, cz]) @@ -1482,17 +1685,13 @@ def _calc_coord(self, xv, yv, renderer=None): 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) + pane_locs_data = [axis.active_pane()[1] for axis in self._axis_map.values()] # Find the distance to the nearest pane by projecting the view vector scales = np.zeros(3) @@ -1500,12 +1699,15 @@ def _calc_coord(self, xv, yv, renderer=None): if vec[i] == 0: scales[i] = np.inf else: - scales[i] = (p1[i] - pane_locs[i]) / vec[i] + scales[i] = (pane_locs_data[i] - p1[i]) / vec[i] pane_idx = np.argmin(abs(scales)) scale = scales[pane_idx] # Calculate the point on the closest pane - p2 = p1 - scale*vec + p2 = p1 + scale * vec + + # Convert from transformed to data coordinates + p2 = np.array(self._untransform_point(p2[0], p2[1], p2[2])) return p2, pane_idx def _arcball(self, x: float, y: float) -> np.ndarray: @@ -1603,6 +1805,11 @@ def _on_move(self, event): q = dq * q elev, azim, roll = np.rad2deg(q.as_cardan_angles()) + step = mpl.rcParams["axes3d.snap_rotation"] + if step > 0 and getattr(event, "key", None) == "control": + elev = step * round(elev / step) + azim = step * round(azim / step) + roll = step * round(roll / step) # update view vertical_axis = self._axis_names[self._vertical_axis] @@ -1660,16 +1867,17 @@ def drag_pan(self, button, key, x, y): 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() + # Calculate pan distance in transformed coordinates for non-linear scales + minx, maxx, miny, maxy, minz, maxz = self._get_scaled_limits() 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) + # Compute new limits in transformed coordinates + self._set_lims_from_transformed( + minx + dx, maxx + dx, + miny + dy, maxy + dy, + minz + dz, maxz + dz) def _calc_view_axes(self, eye): """ @@ -1785,6 +1993,9 @@ def _scale_axis_limits(self, scale_x, scale_y, scale_z): limits by scale factors. A scale factor > 1 zooms out and a scale factor < 1 zooms in. + For non-linear scales, the scaling happens in transformed coordinates to ensure + uniform zoom behavior. + Parameters ---------- scale_x : float @@ -1794,23 +2005,29 @@ def _scale_axis_limits(self, scale_x, scale_y, scale_z): scale_z : float Scale factor for the z data axis. """ - # Get the axis centers and ranges + # Get the axis centers and ranges in transformed coordinates 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) + # Compute new limits in transformed coordinates and set + self._set_lims_from_transformed( + cx - dx*scale_x/2, cx + dx*scale_x/2, + cy - dy*scale_y/2, cy + dy*scale_y/2, + cz - dz*scale_z/2, cz + dz*scale_z/2) 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() + """ + Get 3D world centers and axis ranges in transformed coordinates. + + For non-linear scales (log, symlog, etc.), centers and ranges are + computed in transformed coordinates to ensure uniform zoom/pan behavior. + """ + # Get limits in transformed coordinates for non-linear scale zoom/pan + minx, maxx, miny, maxy, minz, maxz = self._get_scaled_limits() cx = (maxx + minx)/2 cy = (maxy + miny)/2 cz = (maxz + minz)/2 - # Calculate range of axis limits + # Calculate range of axis limits in transformed coordinates dx = (maxx - minx) dy = (maxy - miny) dz = (maxz - minz) @@ -1886,18 +2103,34 @@ 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. See Also -------- - zaxis_inverted + get_zinverted get_zlim, set_zlim get_zbound, set_zbound """ bottom, top = self.get_zlim() self.set_zlim(top, bottom, auto=None) + 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(""" + + .. admonition:: Discouraged + + The use of this method is discouraged. + Use `.Axes3D.get_zinverted` instead. + """)) def get_zbound(self): """ @@ -1940,6 +2173,16 @@ def text(self, x, y, z, s, zdir=None, *, axlim_clip=False, **kwargs): `.Text3D` The created `.Text3D` instance. """ + if 'rotation' in kwargs: + _api.warn_external( + "The `rotation` parameter has not yet been implemented " + "and is currently ignored." + ) + if 'rotation_mode' in kwargs: + _api.warn_external( + "The `rotation_mode` parameter has not yet been implemented " + "and is currently ignored." + ) text = super().text(x, y, s, **kwargs) art3d.text_2d_to_3d(text, z, zdir, axlim_clip) return text @@ -2033,9 +2276,10 @@ def fill_between(self, x1, y1, z1, x2, y2, z2, *, - '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 + facecolors : :mpltype:`color` or list of :mpltype:`color`, optional Colors of each individual patch, or a single color to be used for - all patches. + all patches. If not given, the next color from the patch color + cycle is used. shade : bool, default: None Whether to shade the facecolors. If *None*, then defaults to *True* @@ -2117,7 +2361,7 @@ def fill_between(self, x1, y1, z1, x2, y2, z2, *, polyc = art3d.Poly3DCollection(polys, facecolors=facecolors, shade=shade, axlim_clip=axlim_clip, **kwargs) - self.add_collection(polyc) + self.add_collection(polyc, autolim="_datalim_only") self.auto_scale_xyz([x1, x2], [y1, y2], [z1, z2], had_data) return polyc @@ -2316,7 +2560,7 @@ def plot_surface(self, X, Y, Z, *, norm=None, vmin=None, polys, facecolors=color, shade=shade, lightsource=lightsource, axlim_clip=axlim_clip, **kwargs) - self.add_collection(polyc) + self.add_collection(polyc, autolim="_datalim_only") self.auto_scale_xyz(X, Y, Z, had_data) return polyc @@ -2397,51 +2641,52 @@ def plot_wireframe(self, X, Y, Z, *, axlim_clip=False, **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)]) - + # 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) + self.add_collection(linec, autolim="_datalim_only") return linec @@ -2542,7 +2787,7 @@ def plot_trisurf(self, *args, color=None, norm=None, vmin=None, vmax=None, verts, *args, shade=shade, lightsource=lightsource, facecolors=color, axlim_clip=axlim_clip, **kwargs) - self.add_collection(polyc) + self.add_collection(polyc, autolim="_datalim_only") self.auto_scale_xyz(tri.x, tri.y, z, had_data) return polyc @@ -2872,25 +3117,31 @@ def add_collection3d(self, col, zs=0, zdir='z', autolim=True, *, if autolim: if isinstance(col, art3d.Line3DCollection): - self.auto_scale_xyz(*np.array(col._segments3d).transpose(), - had_data=had_data) + # Handle ragged arrays by extracting coordinates separately + all_points = np.concatenate(col._segments3d) + self.auto_scale_xyz(all_points[:, 0], all_points[:, 1], + all_points[:, 2], had_data=had_data) elif isinstance(col, art3d.Poly3DCollection): - self.auto_scale_xyz(*col._vec[:-1], had_data=had_data) + 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) + collection = super().add_collection(col, autolim="_datalim_only") return collection @_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=True, *args, - axlim_clip=False, **kwargs): + 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. @@ -2922,16 +3173,24 @@ def scatter(self, xs, ys, - 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: :rc:`axes3d.depthshade` Whether to shade the scatter markers to give the appearance of depth. Each call to ``scatter()`` will perform its depthshading independently. + + depthshade_minalpha : float, default: :rc:`axes3d.depthshade_minalpha` + The lowest alpha value applied by depth-shading. + + .. 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 keyword arguments are passed on to `~.axes.Axes.scatter`. @@ -2951,16 +3210,24 @@ def scatter(self, xs, ys, ) 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, - axlim_clip=axlim_clip) - + 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) @@ -3192,7 +3459,7 @@ def bar3d(self, x, y, z, dx, dy, dz, color=None, lightsource=lightsource, axlim_clip=axlim_clip, *args, **kwargs) - self.add_collection(col) + self.add_collection(col, autolim="_datalim_only") self.auto_scale_xyz((minx, maxx), (miny, maxy), (minz, maxz), had_data) @@ -3289,7 +3556,7 @@ def calc_arrows(UVW): if any(len(v) == 0 for v in input_args): # No quivers, so just make an empty collection and return early linec = art3d.Line3DCollection([], **kwargs) - self.add_collection(linec) + self.add_collection(linec, autolim="_datalim_only") return linec shaft_dt = np.array([0., length], dtype=float) @@ -3327,7 +3594,7 @@ def calc_arrows(UVW): lines = [] linec = art3d.Line3DCollection(lines, axlim_clip=axlim_clip, **kwargs) - self.add_collection(linec) + self.add_collection(linec, autolim="_datalim_only") self.auto_scale_xyz(XYZ[:, 0], XYZ[:, 1], XYZ[:, 2], had_data) @@ -3588,12 +3855,12 @@ def errorbar(self, x, y, z, zerr=None, yerr=None, xerr=None, fmt='', Use 'none' (case-insensitive) to plot errorbars without any data markers. - ecolor : :mpltype:`color`, default: None - The color of the errorbar lines. If None, use the color of the + ecolor : :mpltype:`color`, optional + The color of the errorbar lines. If not given, use the color of the line connecting the markers. - elinewidth : float, default: None - The linewidth of the errorbar lines. If None, the linewidth of + elinewidth : float, optional + The linewidth of the errorbar lines. If not given, the linewidth of the current style is used. capsize : float, default: :rc:`errorbar.capsize` @@ -3858,7 +4125,7 @@ def _extract_errs(err, data, lomask, himask): errline = art3d.Line3DCollection(np.array(coorderr).T, axlim_clip=axlim_clip, **eb_lines_style) - self.add_collection(errline) + self.add_collection(errline, autolim="_datalim_only") errlines.append(errline) coorderrs.append(coorderr) @@ -4000,8 +4267,7 @@ def stem(self, x, y, z, *, linefmt='C0-', markerfmt='C0o', basefmt='C3-', # Determine style for stem lines. linestyle, linemarker, linecolor = _process_plot_format(linefmt) - if linestyle is None: - linestyle = mpl.rcParams['lines.linestyle'] + linestyle = mpl._val_or_rc(linestyle, 'lines.linestyle') # Plot everything in required order. baseline, = self.plot(basex, basey, basefmt, zs=bottom, @@ -4009,7 +4275,7 @@ def stem(self, x, y, z, *, linefmt='C0-', markerfmt='C0o', basefmt='C3-', stemlines = art3d.Line3DCollection( lines, linestyles=linestyle, colors=linecolor, label='_nolegend_', axlim_clip=axlim_clip) - self.add_collection(stemlines) + self.add_collection(stemlines, autolim="_datalim_only") markerline, = self.plot(x, y, z, markerfmt, label='_nolegend_') stem_container = StemContainer((markerline, stemlines, baseline), diff --git a/lib/mpl_toolkits/mplot3d/axis3d.py b/lib/mpl_toolkits/mplot3d/axis3d.py index 4da5031b990c..0ac2e50b1a1a 100644 --- a/lib/mpl_toolkits/mplot3d/axis3d.py +++ b/lib/mpl_toolkits/mplot3d/axis3d.py @@ -267,14 +267,15 @@ def get_rotate_label(self, text): return len(text) > 4 def _get_coord_info(self): - mins, maxs = np.array([ - self.axes.get_xbound(), - self.axes.get_ybound(), - self.axes.get_zbound(), - ]).T - - # Project the bounds along the current position of the cube: - bounds = mins[0], maxs[0], mins[1], maxs[1], mins[2], maxs[2] + # Get scaled limits directly from the axes helper + xmin, xmax, ymin, ymax, zmin, zmax = self.axes._get_scaled_limits() + mins = np.array([xmin, ymin, zmin]) + maxs = np.array([xmax, ymax, zmax]) + + # Get data-space bounds for _transformed_cube + bounds = (*self.axes.get_xbound(), + *self.axes.get_ybound(), + *self.axes.get_zbound()) bounds_proj = self.axes._transformed_cube(bounds) # Determine which one of the parallel planes are higher up: @@ -443,6 +444,10 @@ def _draw_ticks(self, renderer, edgep1, centers, deltas, highs, mins, maxs, tc, highs = self._get_coord_info() centers, deltas = self._calc_centers_deltas(maxs, mins) + # Get the scale transform for this axis to transform tick locations + axis = [self.axes.xaxis, self.axes.yaxis, self.axes.zaxis][index] + axis_trans = axis.get_transform() + # Draw ticks: tickdir = self._get_tickdir(pos) tickdelta = deltas[tickdir] if highs[tickdir] else -deltas[tickdir] @@ -457,10 +462,11 @@ def _draw_ticks(self, renderer, edgep1, centers, deltas, highs, default_label_offset = 8. # A rough estimate points = deltas_per_point * deltas + # All coordinates below are in transformed coordinates for proper projection for tick in ticks: # Get tick line positions pos = edgep1.copy() - pos[index] = tick.get_loc() + pos[index] = axis_trans.transform([tick.get_loc()])[0] pos[tickdir] = out_tickdir x1, y1, z1 = proj3d.proj_transform(*pos, self.axes.M) pos[tickdir] = in_tickdir @@ -468,7 +474,6 @@ def _draw_ticks(self, renderer, edgep1, centers, deltas, highs, # Get position of label labeldeltas = (tick.get_pad() + default_label_offset) * points - pos[tickdir] = edgep1_tickdir pos = _move_from_center(pos, centers, labeldeltas, self._axmask()) lx, ly, lz = proj3d.proj_transform(*pos, self.axes.M) @@ -642,10 +647,15 @@ def draw_grid(self, renderer): info = self._axinfo index = info["i"] + # Grid lines use data-space bounds (Line3DCollection applies transforms) mins, maxs, tc, highs = self._get_coord_info() - - minmax = np.where(highs, maxs, mins) - maxmin = np.where(~highs, maxs, mins) + xlim, ylim, zlim = (self.axes.get_xbound(), + self.axes.get_ybound(), + self.axes.get_zbound()) + data_mins = np.array([xlim[0], ylim[0], zlim[0]]) + data_maxs = np.array([xlim[1], ylim[1], zlim[1]]) + minmax = np.where(highs, data_maxs, data_mins) + maxmin = np.where(~highs, data_maxs, data_mins) # Grid points where the planes meet xyz0 = np.tile(minmax, (len(ticks), 1)) @@ -708,6 +718,8 @@ def get_tightbbox(self, renderer=None, *, for_layout_only=False): bb_1, bb_2 = self._get_ticklabel_bboxes(ticks, renderer) other = [] + if self.offsetText.get_visible() and self.offsetText.get_text(): + other.append(self.offsetText.get_window_extent(renderer)) if self.line.get_visible(): other.append(self.line.get_window_extent(renderer)) if (self.label.get_visible() and not for_layout_only and diff --git a/lib/mpl_toolkits/mplot3d/proj3d.py b/lib/mpl_toolkits/mplot3d/proj3d.py index 923bd32c9ce0..81a5aacbdded 100644 --- a/lib/mpl_toolkits/mplot3d/proj3d.py +++ b/lib/mpl_toolkits/mplot3d/proj3d.py @@ -131,23 +131,64 @@ def _ortho_transformation(zfront, zback): return proj_matrix +def _apply_scale_transforms(xs, ys, zs, axes): + """ + Apply axis scale transforms to 3D coordinates. + + Transforms data coordinates to transformed coordinates (applying log, + symlog, etc.) for 3D projection. Preserves masked arrays. + """ + def transform_coord(coord, axis): + coord = np.asanyarray(coord) + data = np.ma.getdata(coord).ravel() + return axis.get_transform().transform(data).reshape(coord.shape) + + xs_scaled = transform_coord(xs, axes.xaxis) + ys_scaled = transform_coord(ys, axes.yaxis) + zs_scaled = transform_coord(zs, axes.zaxis) + + # Preserve combined mask from any masked input + masks = [np.ma.getmask(a) for a in [xs, ys, zs]] + if any(m is not np.ma.nomask for m in masks): + combined = np.ma.mask_or(np.ma.mask_or(masks[0], masks[1]), masks[2]) + xs_scaled = np.ma.array(xs_scaled, mask=combined) + ys_scaled = np.ma.array(ys_scaled, mask=combined) + zs_scaled = np.ma.array(zs_scaled, mask=combined) + + return xs_scaled, ys_scaled, zs_scaled + + def _proj_transform_vec(vec, M): vecw = np.dot(M, vec.data) - w = vecw[3] - txs, tys, tzs = vecw[0]/w, vecw[1]/w, vecw[2]/w - if np.ma.isMA(vec[0]): # we check each to protect for scalars - txs = np.ma.array(txs, mask=vec[0].mask) - if np.ma.isMA(vec[1]): - tys = np.ma.array(tys, mask=vec[1].mask) - if np.ma.isMA(vec[2]): - tzs = np.ma.array(tzs, mask=vec[2].mask) - return txs, tys, tzs + 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 _scale_proj_transform_vectors(vecs, axes): + """ + Apply scale transforms and project vectors. + + Parameters + ---------- + vecs : ... x 3 np.ndarray + Input vectors. + axes : Axes3D + The 3D axes (used for scale transforms and projection matrix). + """ + result_shape = vecs.shape + xs, ys, zs = _apply_scale_transforms( + vecs[..., 0], vecs[..., 1], vecs[..., 2], axes) + vec = _vec_pad_ones(xs.ravel(), ys.ravel(), zs.ravel()) + product = np.dot(axes.M, vec) + tvecs = product[:3] / product[3] + return tvecs.T.reshape(result_shape) def _proj_transform_vec_clip(vec, M, focal_length): vecw = np.dot(M, vec.data) - w = vecw[3] - txs, tys, tzs = vecw[0] / w, vecw[1] / w, vecw[2] / w + 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: @@ -196,24 +237,33 @@ def proj_transform(xs, ys, zs, M): @_api.deprecated("3.10") def proj_transform_clip(xs, ys, zs, M): - return _proj_transform_clip(xs, ys, zs, M, focal_length=np.inf) + vec = _vec_pad_ones(xs, ys, zs) + return _proj_transform_vec_clip(vec, M, focal_length=np.inf) -def _proj_transform_clip(xs, ys, zs, M, focal_length): +def _scale_proj_transform_clip(xs, ys, zs, axes): """ - Transform the points by the projection matrix - and return the clipping result - returns txs, tys, tzs, tis + Apply scale transforms, project, and return clipping result. + + Returns txs, tys, tzs, tis. """ + xs, ys, zs = _apply_scale_transforms(xs, ys, zs, axes) vec = _vec_pad_ones(xs, ys, zs) - return _proj_transform_vec_clip(vec, M, focal_length) - - -def _proj_points(points, M): - return np.column_stack(_proj_trans_points(points, M)) + return _proj_transform_vec_clip(vec, axes.M, axes._focal_length) 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 _scale_proj_transform(xs, ys, zs, axes): + """ + Apply scale transforms and project. + + Combines `_apply_scale_transforms` and `proj_transform` into a single + call. Returns txs, tys, tzs. + """ + xs, ys, zs = _apply_scale_transforms(xs, ys, zs, axes) + return proj_transform(xs, ys, zs, axes.M) 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 index 5d58cea8bccf..af8cc16b14cc 100644 Binary files a/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/quiver3d.png 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/scale3d_all_scales.png b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/scale3d_all_scales.png new file mode 100644 index 000000000000..af1411dbfc9c Binary files /dev/null and b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/scale3d_all_scales.png differ diff --git a/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/scale3d_artists_log.png b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/scale3d_artists_log.png new file mode 100644 index 000000000000..e5180b57fa9a Binary files /dev/null and b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/scale3d_artists_log.png differ diff --git a/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/scale3d_log_bases.png b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/scale3d_log_bases.png new file mode 100644 index 000000000000..875c91e07f67 Binary files /dev/null and b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/scale3d_log_bases.png differ diff --git a/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/scale3d_symlog_params.png b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/scale3d_symlog_params.png new file mode 100644 index 000000000000..73732dea1284 Binary files /dev/null and b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/scale3d_symlog_params.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 index ed8b3831726e..aa15bb95168c 100644 Binary files a/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/scatter3d.png 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 index 2d35d95e68bd..f295ec7132ba 100644 Binary files a/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/scatter3d_color.png 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 index 15cc2d77a2ac..676ee10370f6 100644 Binary files a/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/scatter3d_linewidth.png 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 index 8e8df221d640..ee562e27242b 100644 Binary files a/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/scatter_spiral.png 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/surface3d_masked.png b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/surface3d_masked.png index df893f9c843f..9e5af36ffbfc 100644 Binary files a/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/surface3d_masked.png 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/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 index 0623cad002e8..7e4cf6a0c014 100644 Binary files a/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/wireframe3dzerocstride.png 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_legend3d/fancy.png b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_legend3d/fancy.png index b9b0fb6ef094..62e7dbc6cdae 100644 Binary files a/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_legend3d/fancy.png and b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_legend3d/fancy.png differ diff --git a/lib/mpl_toolkits/mplot3d/tests/conftest.py b/lib/mpl_toolkits/mplot3d/tests/conftest.py index 61c2de3e07ba..12eaac9ce2f4 100644 --- a/lib/mpl_toolkits/mplot3d/tests/conftest.py +++ b/lib/mpl_toolkits/mplot3d/tests/conftest.py @@ -1,2 +1,3 @@ -from matplotlib.testing.conftest import (mpl_test_settings, # noqa - pytest_configure, pytest_unconfigure) +from matplotlib.testing.conftest import ( # noqa + pytest_configure, pytest_unconfigure, + high_memory, mpl_test_settings) diff --git a/lib/mpl_toolkits/mplot3d/tests/test_art3d.py b/lib/mpl_toolkits/mplot3d/tests/test_art3d.py index f4f7067b76bb..aca943f9e0c0 100644 --- a/lib/mpl_toolkits/mplot3d/tests/test_art3d.py +++ b/lib/mpl_toolkits/mplot3d/tests/test_art3d.py @@ -1,9 +1,30 @@ import numpy as np +import numpy.testing as nptest +import pytest import matplotlib.pyplot as plt from matplotlib.backend_bases import MouseEvent -from mpl_toolkits.mplot3d.art3d import Line3DCollection, _all_points_on_plane +from mpl_toolkits.mplot3d.art3d import ( + get_dir_vector, + Line3DCollection, + Poly3DCollection, + _all_points_on_plane, +) + + +@pytest.mark.parametrize("zdir, expected", [ + ("x", (1, 0, 0)), + ("y", (0, 1, 0)), + ("z", (0, 0, 1)), + (None, (0, 0, 0)), + ((1, 2, 3), (1, 2, 3)), + (np.array([4, 5, 6]), (4, 5, 6)), +]) +def test_get_dir_vector(zdir, expected): + res = get_dir_vector(zdir) + assert isinstance(res, np.ndarray) + nptest.assert_array_equal(res, expected) def test_scatter_3d_projection_conservation(): @@ -51,7 +72,7 @@ def test_zordered_error(): fig = plt.figure() ax = fig.add_subplot(projection="3d") - ax.add_collection(Line3DCollection(lc)) + ax.add_collection(Line3DCollection(lc), autolim="_datalim_only") ax.scatter(*pc, visible=False) plt.draw() @@ -85,3 +106,14 @@ def test_all_points_on_plane(): # 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 index ad952e4395af..8d2441393dde 100644 --- a/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py +++ b/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py @@ -3,6 +3,7 @@ import platform import sys +from packaging.version import parse as parse_version import pytest from mpl_toolkits.mplot3d import Axes3D, axes3d, proj3d, art3d @@ -13,11 +14,11 @@ 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 +from matplotlib import _api import matplotlib.pyplot as plt import numpy as np @@ -36,7 +37,7 @@ def plot_cuboid(ax, scale): ax.plot3D(*zip(start*np.array(scale), end*np.array(scale))) -@check_figures_equal(extensions=["png"]) +@check_figures_equal() def test_invisible_axes(fig_test, fig_ref): ax = fig_test.subplots(subplot_kw=dict(projection='3d')) ax.set_visible(False) @@ -181,7 +182,8 @@ def test_bar3d_shaded(): fig.canvas.draw() -@mpl3d_image_comparison(['bar3d_notshaded.png'], style='mpl20') +@mpl3d_image_comparison(['bar3d_notshaded.png'], style='mpl20', + tol=0.01 if parse_version(np.version.version).major < 2 else 0) def test_bar3d_notshaded(): fig = plt.figure() ax = fig.add_subplot(projection='3d') @@ -221,17 +223,16 @@ def test_bar3d_lightsource(): np.testing.assert_array_max_ulp(color, collection._facecolor3d[1::6], 4) -@mpl3d_image_comparison( - ['contour3d.png'], style='mpl20', - tol=0.002 if platform.machine() in ('aarch64', 'arm64', 'ppc64le', 's390x') else 0) +@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=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.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) @@ -241,7 +242,7 @@ def test_contour3d_extend3d(): 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=cm.coolwarm, extend3d=True) + 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) @@ -253,9 +254,9 @@ def test_contourf3d(): 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=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.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) @@ -271,7 +272,7 @@ def test_contourf3d_fill(): # 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.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) @@ -280,24 +281,17 @@ def test_contourf3d_fill(): @pytest.mark.parametrize('extend, levels', [['both', [2, 4, 6]], ['min', [2, 4, 6, 8]], ['max', [0, 2, 4, 6]]]) -@check_figures_equal(extensions=["png"]) +@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_ref.contourf(X, Y, Z, levels=[0, 2, 4, 6, 8], vmin=1, vmax=7) ax_test = fig_test.add_subplot(projection='3d') - ax_test.contourf(X, Y, Z, levels, extend=extend, **kwargs) + ax_test.contourf(X, Y, Z, levels, extend=extend, vmin=1, vmax=7) for ax in [ax_ref, ax_test]: ax.set_xlim(-2, 2) @@ -344,7 +338,7 @@ def test_lines3d(): ax.plot(x, y, z) -@check_figures_equal(extensions=["png"]) +@check_figures_equal() def test_plot_scalar(fig_test, fig_ref): ax1 = fig_test.add_subplot(projection='3d') ax1.plot([1], [1], "o") @@ -394,7 +388,7 @@ def f(t): ax.set_zlim3d(-1, 1) -@check_figures_equal(extensions=['png']) +@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. @@ -409,7 +403,6 @@ def test_tight_layout_text(fig_test, fig_ref): @mpl3d_image_comparison(['scatter3d.png'], style='mpl20') def test_scatter3d(): - plt.rcParams['axes3d.automargin'] = True # Remove when image is regenerated fig = plt.figure() ax = fig.add_subplot(projection='3d') ax.scatter(np.arange(10), np.arange(10), np.arange(10), @@ -423,7 +416,6 @@ def test_scatter3d(): @mpl3d_image_comparison(['scatter3d_color.png'], style='mpl20') def test_scatter3d_color(): - plt.rcParams['axes3d.automargin'] = True # Remove when image is regenerated fig = plt.figure() ax = fig.add_subplot(projection='3d') @@ -448,7 +440,7 @@ def test_scatter3d_linewidth(): marker='o', linewidth=np.arange(10)) -@check_figures_equal(extensions=['png']) +@check_figures_equal() def test_scatter3d_linewidth_modification(fig_ref, fig_test): # Changing Path3DCollection linewidths with array-like post-creation # should work correctly. @@ -462,12 +454,12 @@ def test_scatter3d_linewidth_modification(fig_ref, fig_test): linewidths=np.arange(10)) -@check_figures_equal(extensions=['png']) +@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') + marker='o', depthshade=True) c.set_facecolor('C1') c.set_edgecolor('C2') c.set_alpha([0.3, 0.7] * 5) @@ -483,13 +475,13 @@ def test_scatter3d_modification(fig_ref, fig_test): depthshade=False, s=75, linewidths=3) -@pytest.mark.parametrize('depthshade', [True, False]) -@check_figures_equal(extensions=['png']) -def test_scatter3d_sorting(fig_ref, fig_test, depthshade): +@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 @@ -540,7 +532,7 @@ def test_scatter3d_sorting(fig_ref, fig_test, depthshade): @pytest.mark.parametrize('azim', [-50, 130]) # yellow first, blue first -@check_figures_equal(extensions=['png']) +@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. @@ -560,7 +552,7 @@ def test_marker_draw_order_data_reversed(fig_test, fig_ref, azim): ax.view_init(elev=0, azim=azim, roll=0) -@check_figures_equal(extensions=['png']) +@check_figures_equal() def test_marker_draw_order_view_rotated(fig_test, fig_ref): """ Test that the draw order changes with the direction. @@ -648,14 +640,15 @@ def test_surface3d(): 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, + 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') +# TODO: tighten tolerance after baseline image is regenerated for text overhaul +@image_comparison(['surface3d_label_offset_tick_position.png'], style='mpl20', tol=0.07) def test_surface3d_label_offset_tick_position(): plt.rcParams['axes3d.automargin'] = True # Remove when image is regenerated ax = plt.figure().add_subplot(projection="3d") @@ -711,7 +704,7 @@ def test_surface3d_masked(): ax.view_init(30, -80, 0) -@check_figures_equal(extensions=["png"]) +@check_figures_equal() def test_plot_scatter_masks(fig_test, fig_ref): x = np.linspace(0, 10, 100) y = np.linspace(0, 10, 100) @@ -729,7 +722,7 @@ def test_plot_scatter_masks(fig_test, fig_ref): ax_ref.plot(x, y, z) -@check_figures_equal(extensions=["png"]) +@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 @@ -751,7 +744,8 @@ def test_surface3d_masked_strides(): ax.view_init(60, -45, 0) -@mpl3d_image_comparison(['text3d.png'], remove_text=False, style='mpl20') +# TODO: tighten tolerance after baseline image is regenerated for text overhaul +@mpl3d_image_comparison(['text3d.png'], remove_text=False, style='mpl20', tol=0.1) def test_text3d(): fig = plt.figure() ax = fig.add_subplot(projection='3d') @@ -776,7 +770,7 @@ def test_text3d(): ax.set_zlabel('Z axis') -@check_figures_equal(extensions=['png']) +@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. @@ -816,7 +810,7 @@ def test_trisurf3d(): fig = plt.figure() ax = fig.add_subplot(projection='3d') - ax.plot_trisurf(x, y, z, cmap=cm.jet, linewidth=0.2) + ax.plot_trisurf(x, y, z, cmap="jet", linewidth=0.2) @mpl3d_image_comparison(['trisurf3d_shaded.png'], tol=0.03, style='mpl20') @@ -845,6 +839,14 @@ def test_wireframe3d(): 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() @@ -882,7 +884,6 @@ def test_mixedsamplesraises(): # remove tolerance when regenerating the test image @mpl3d_image_comparison(['quiver3d.png'], style='mpl20', tol=0.003) def test_quiver3d(): - plt.rcParams['axes3d.automargin'] = True # Remove when image is regenerated fig = plt.figure() ax = fig.add_subplot(projection='3d') pivots = ['tip', 'middle', 'tail'] @@ -902,7 +903,7 @@ def test_quiver3d(): ax.set_zlim(-1, 5) -@check_figures_equal(extensions=["png"]) +@check_figures_equal() def test_quiver3d_empty(fig_test, fig_ref): fig_ref.add_subplot(projection='3d') x = y = z = u = v = w = [] @@ -936,7 +937,7 @@ def test_quiver3d_colorcoded(): x = y = dx = dz = np.zeros(10) z = dy = np.arange(10.) - color = plt.cm.Reds(dy/dy.max()) + color = plt.colormaps["Reds"](dy/dy.max()) ax.quiver(x, y, z, dx, dy, dz, colors=color) ax.set_ylim(0, 10) @@ -954,13 +955,13 @@ def test_patch_modification(): assert mcolors.same_color(circle.get_facecolor(), (1, 0, 0, 1)) -@check_figures_equal(extensions=['png']) +@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) + c = art3d.Patch3DCollection([patch1, patch2], linewidths=3, depthshade=True) ax_test = fig_test.add_subplot(projection='3d') ax_test.add_collection3d(c) @@ -988,7 +989,7 @@ def test_poly3dcollection_verts_validation(): art3d.Poly3DCollection(poly) # should be Poly3DCollection([poly]) poly = np.array(poly, dtype=float) - with pytest.raises(ValueError, match=r'list of \(N, 3\) array-like'): + with pytest.raises(ValueError, match=r'shape \(M, N, 3\)'): art3d.Poly3DCollection(poly) # should be Poly3DCollection([poly]) @@ -1123,8 +1124,9 @@ def test_poly3dCollection_autoscaling(): assert np.allclose(ax.get_zlim3d(), (-0.0833333333333333, 4.083333333333333)) +# TODO: tighten tolerance after baseline image is regenerated for text overhaul @mpl3d_image_comparison(['axes3d_labelpad.png'], - remove_text=False, style='mpl20') + remove_text=False, style='mpl20', tol=0.06) def test_axes3d_labelpad(): fig = plt.figure() ax = fig.add_axes(Axes3D(fig)) @@ -1214,7 +1216,7 @@ def _test_proj_draw_axes(M, s=1, *args, **kwargs): fig, ax = plt.subplots(*args, **kwargs) linec = LineCollection(lines) - ax.add_collection(linec) + ax.add_collection(linec, autolim="_datalim_only") for x, y, t in zip(txs, tys, ['o', 'x', 'y', 'z']): ax.text(x, y, t) @@ -1324,7 +1326,7 @@ def test_unautoscale(axis, auto): np.testing.assert_array_equal(get_lim(), (-0.5, 0.5)) -@check_figures_equal(extensions=["png"]) +@check_figures_equal() def test_culling(fig_test, fig_ref): xmins = (-100, -50) for fig, xmin in zip((fig_test, fig_ref), xmins): @@ -1379,7 +1381,7 @@ def test_axes3d_isometric(): ax.grid(True) -@check_figures_equal(extensions=["png"]) +@check_figures_equal() def test_axlim_clip(fig_test, fig_ref): # With axlim clipping ax = fig_test.add_subplot(projection="3d") @@ -1577,7 +1579,7 @@ def test_line3d_set_get_data_3d(): np.testing.assert_array_equal((x, y, np.zeros_like(z)), line.get_data_3d()) -@check_figures_equal(extensions=["png"]) +@check_figures_equal() def test_inverted(fig_test, fig_ref): # Plot then invert. ax = fig_test.add_subplot(projection="3d") @@ -1626,7 +1628,7 @@ def test_ax3d_tickcolour(): assert tick.tick1line._color == 'red' -@check_figures_equal(extensions=["png"]) +@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: @@ -1666,7 +1668,7 @@ def get_formatters(ax, names): not mpl.rcParams["axes.formatter.use_mathtext"]) -@check_figures_equal(extensions=["png"]) +@check_figures_equal() def test_quiver3D_smoke(fig_test, fig_ref): pivot = "middle" # Make the grid @@ -1713,7 +1715,7 @@ def test_errorbar3d_errorevery(): @mpl3d_image_comparison(['errorbar3d.png'], style='mpl20', - tol=0.02 if platform.machine() == 'arm64' else 0) + 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() @@ -1729,7 +1731,7 @@ def test_errorbar3d(): ax.legend() -@image_comparison(['stem3d.png'], style='mpl20', tol=0.008) +@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), @@ -1863,7 +1865,7 @@ def test_set_zlim(): ax.set_zlim(top=0, zmax=1) -@check_figures_equal(extensions=["png"]) +@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") @@ -2008,16 +2010,16 @@ def test_rotate(style): 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)) + MouseEvent._from_ax_coords( + "button_press_event", ax, (0, 0), MouseButton.LEFT)._process() + MouseEvent._from_ax_coords( + "motion_notify_event", ax, (s*dx*ax._pseudo_w, s*dy*ax._pseudo_h), + MouseButton.LEFT)._process() ax.figure.canvas.draw() c = np.sqrt(3)/2 expectations = { - ('azel', 0, 1, 0): (0, -45, 0), + ('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), @@ -2072,10 +2074,10 @@ def convert_lim(dmin, dmax): 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)) + MouseEvent._from_ax_coords( + "button_press_event", ax, (0, 0), MouseButton.MIDDLE)._process() + MouseEvent._from_ax_coords( + "motion_notify_event", ax, (1, 1), MouseButton.MIDDLE)._process() x_center, x_range = convert_lim(*ax.get_xlim3d()) y_center, y_range = convert_lim(*ax.get_ylim3d()) @@ -2130,20 +2132,20 @@ def test_toolbar_zoom_pan(tool, button, key, expected): # 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() - tb.press_zoom(start_event) - tb.drag_zoom(stop_event) - tb.release_zoom(stop_event) else: tb.pan() - tb.press_pan(start_event) - tb.drag_pan(stop_event) - tb.release_pan(stop_event) + + 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 @@ -2169,7 +2171,7 @@ def test_toolbar_zoom_pan(tool, button, key, expected): @mpl.style.context('default') -@check_figures_equal(extensions=["png"]) +@check_figures_equal() def test_scalarmap_update(fig_test, fig_ref): x, y, z = np.array(list(itertools.product(*[np.arange(0, 5, 1), @@ -2198,9 +2200,7 @@ def test_subfigure_simple(): 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')) +@image_comparison(['computed_zorder.png'], remove_text=True, style='mpl20') def test_computed_zorder(): plt.rcParams['axes3d.automargin'] = True # Remove when image is regenerated fig = plt.figure() @@ -2223,9 +2223,9 @@ def test_computed_zorder(): # 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)) + 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') @@ -2369,7 +2369,7 @@ def test_margins_errors(err, args, kwargs, match): ax.margins(*args, **kwargs) -@check_figures_equal(extensions=["png"]) +@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$') @@ -2390,7 +2390,7 @@ def test_draw_single_lines_from_Nx1(): ax.plot([[0], [1]], [[0], [1]], [[0], [1]]) -@check_figures_equal(extensions=["png"]) +@check_figures_equal() def test_pathpatch_3d(fig_test, fig_ref): ax = fig_ref.add_subplot(projection="3d") path = Path.unit_rectangle() @@ -2549,11 +2549,10 @@ def test_on_move_vertical_axis(vertical_axis: str) -> None: 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) + MouseEvent._from_ax_coords( + "button_press_event", ax, (0, 1), MouseButton.LEFT)._process() + MouseEvent._from_ax_coords( + "motion_notify_event", ax, (.5, .8), MouseButton.LEFT)._process() assert ax._axis_names.index(vertical_axis) == ax._vertical_axis @@ -2604,7 +2603,7 @@ def test_panecolor_rcparams(): fig.add_subplot(projection='3d') -@check_figures_equal(extensions=["png"]) +@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 @@ -2687,3 +2686,507 @@ def test_ndarray_color_kwargs_value_error(): ax = fig.add_subplot(111, projection='3d') ax.scatter(1, 0, 0, color=np.array([0, 0, 0, 1])) fig.canvas.draw() + + +def test_line3dcollection_autolim_ragged(): + """Test Line3DCollection with autolim=True and lines of different lengths.""" + fig = plt.figure() + ax = fig.add_subplot(projection='3d') + + # Create lines with different numbers of points (ragged arrays) + edges = [ + [(0, 0, 0), (1, 1, 1), (2, 2, 2)], # 3 points + [(0, 1, 0), (1, 2, 1)], # 2 points + [(1, 0, 1), (2, 1, 2), (3, 2, 3), (4, 3, 4)] # 4 points + ] + + # This should not raise an exception. + collections = ax.add_collection3d(art3d.Line3DCollection(edges), autolim=True) + + # Check that limits were computed correctly with margins + # The limits should include all points with default margins + assert np.allclose(ax.get_xlim3d(), (-0.08333333333333333, 4.083333333333333)) + assert np.allclose(ax.get_ylim3d(), (-0.0625, 3.0625)) + assert np.allclose(ax.get_zlim3d(), (-0.08333333333333333, 4.083333333333333)) + + +def test_axes3d_set_aspect_deperecated_params(): + """ + Test that using the deprecated 'anchor' and 'share' kwargs in + set_aspect raises the correct warning. + """ + fig = plt.figure() + ax = fig.add_subplot(projection='3d') + + # Test that providing the `anchor` parameter raises a deprecation warning. + with pytest.warns(_api.MatplotlibDeprecationWarning, match="'anchor' parameter"): + ax.set_aspect('equal', anchor='C') + + # Test that using the 'share' parameter is now deprecated. + with pytest.warns(_api.MatplotlibDeprecationWarning, match="'share' parameter"): + ax.set_aspect('equal', share=True) + + # Test that the `adjustable` parameter is correctly processed to satisfy + # code coverage. + ax.set_aspect('equal', adjustable='box') + assert ax.get_adjustable() == 'box' + + ax.set_aspect('equal', adjustable='datalim') + assert ax.get_adjustable() == 'datalim' + + with pytest.raises(ValueError, match="adjustable"): + ax.set_aspect('equal', adjustable='invalid_value') + + +def test_axis_get_tightbbox_includes_offset_text(): + # Test that axis.get_tightbbox includes the offset_text + # Regression test for issue #30744 + fig = plt.figure() + ax = fig.add_subplot(111, projection='3d') + + # Create data with high precision values that trigger offset text + Z = np.array([[0.1, 0.100000001], [0.100000000001, 0.100000000]]) + ny, nx = Z.shape + x = np.arange(nx) + y = np.arange(ny) + X, Y = np.meshgrid(x, y) + + ax.plot_surface(X, Y, Z) + + # Force a draw to ensure offset text is created and positioned + fig.canvas.draw() + renderer = fig.canvas.get_renderer() + + # Get the z-axis (which should have the offset text) + zaxis = ax.zaxis + + # Check that offset text is visible and has content + # The offset text may not be visible on all backends/configurations, + # so we only test the inclusion when it's actually present + if (zaxis.offsetText.get_visible() and + zaxis.offsetText.get_text()): + offset_bbox = zaxis.offsetText.get_window_extent(renderer) + + # Get the tight bbox - this should include the offset text + bbox = zaxis.get_tightbbox(renderer) + assert bbox is not None + assert offset_bbox is not None + + # The tight bbox should fully contain the offset text bbox + # Check that offset_bbox is within bbox bounds (with small tolerance for + # floating point errors) + assert bbox.x0 <= offset_bbox.x0 + 1e-6, \ + f"bbox.x0 ({bbox.x0}) should be <= offset_bbox.x0 ({offset_bbox.x0})" + assert bbox.y0 <= offset_bbox.y0 + 1e-6, \ + f"bbox.y0 ({bbox.y0}) should be <= offset_bbox.y0 ({offset_bbox.y0})" + assert bbox.x1 >= offset_bbox.x1 - 1e-6, \ + f"bbox.x1 ({bbox.x1}) should be >= offset_bbox.x1 ({offset_bbox.x1})" + assert bbox.y1 >= offset_bbox.y1 - 1e-6, \ + f"bbox.y1 ({bbox.y1}) should be >= offset_bbox.y1 ({offset_bbox.y1})" + + +def test_ctrl_rotation_snaps_to_5deg(): + fig = plt.figure() + ax = fig.add_subplot(projection='3d') + + initial = (12.3, 33.7, 2.2) + ax.view_init(*initial) + fig.canvas.draw() + + s = 0.25 + step = plt.rcParams["axes3d.snap_rotation"] + + # First rotation without Ctrl + with mpl.rc_context({'axes3d.mouserotationstyle': 'azel'}): + MouseEvent._from_ax_coords( + "button_press_event", ax, (0, 0), MouseButton.LEFT + )._process() + + MouseEvent._from_ax_coords( + "motion_notify_event", + ax, + (s * ax._pseudo_w, s * ax._pseudo_h), + MouseButton.LEFT, + )._process() + + fig.canvas.draw() + + rotated_elev = ax.elev + rotated_azim = ax.azim + rotated_roll = ax.roll + + # Reset before ctrl rotation + ax.view_init(*initial) + fig.canvas.draw() + + # Now rotate with Ctrl + with mpl.rc_context({'axes3d.mouserotationstyle': 'azel'}): + MouseEvent._from_ax_coords( + "button_press_event", ax, (0, 0), MouseButton.LEFT + )._process() + + MouseEvent._from_ax_coords( + "motion_notify_event", + ax, + (s * ax._pseudo_w, s * ax._pseudo_h), + MouseButton.LEFT, + key="control" + )._process() + + fig.canvas.draw() + + expected_elev = step * round(rotated_elev / step) + expected_azim = step * round(rotated_azim / step) + expected_roll = step * round(rotated_roll / step) + + assert ax.elev == pytest.approx(expected_elev) + assert ax.azim == pytest.approx(expected_azim) + assert ax.roll == pytest.approx(expected_roll) + + plt.close(fig) + + +# ============================================================================= +# Tests for 3D scale transforms (log, symlog, logit, etc.) +# ============================================================================= + +def _make_log_data(): + """Data spanning 1 to ~1000 for log scale.""" + t = np.linspace(0, 2 * np.pi, 50) + x = 10 ** (t / 2) + y = 10 ** (1 + np.sin(t)) + z = 10 ** (2 * (1 + np.cos(t) / 2)) + return x, y, z + + +def _make_surface_log_data(): + """Grid data for surface with positive Z.""" + x = np.linspace(1, 10, 20) + y = np.linspace(1, 10, 20) + X, Y = np.meshgrid(x, y) + Z = X * Y + return X, Y, Z + + +def _make_triangulation_data(): + """Data for trisurf with positive values.""" + np.random.seed(42) + x = np.random.uniform(1, 100, 100) + y = np.random.uniform(1, 100, 100) + z = x * y / 10 + return x, y, z + + +@mpl3d_image_comparison(['scale3d_artists_log.png'], style='mpl20', + remove_text=False, tol=0.03) +def test_scale3d_artists_log(): + """Test all 3D artist types with log scale.""" + fig = plt.figure(figsize=(16, 12)) + log_kw = dict(xscale='log', yscale='log', zscale='log') + line_data = _make_log_data() + surf_X, surf_Y, surf_Z = _make_surface_log_data() + + # Row 1: plot, wireframe, scatter, bar3d + ax = fig.add_subplot(3, 4, 1, projection='3d') + ax.plot(*line_data) + ax.set(**log_kw, title='plot') + + ax = fig.add_subplot(3, 4, 2, projection='3d') + ax.plot_wireframe(surf_X, surf_Y, surf_Z, rstride=5, cstride=5) + ax.set(**log_kw, title='wireframe') + + ax = fig.add_subplot(3, 4, 3, projection='3d') + ax.scatter(*line_data, c=line_data[2], cmap='viridis') + ax.set(**log_kw, title='scatter') + + ax = fig.add_subplot(3, 4, 4, projection='3d') + bx, by = np.meshgrid([1, 10, 100], [1, 10, 100]) + bx, by = bx.flatten(), by.flatten() + ax.bar3d(bx, by, np.ones_like(bx, dtype=float), + bx * 0.3, by * 0.3, bx * by / 10, alpha=0.8) + ax.set(**log_kw, title='bar3d') + + # Row 2: surface, trisurf, contour, contourf + ax = fig.add_subplot(3, 4, 5, projection='3d') + ax.plot_surface(surf_X, surf_Y, surf_Z, cmap='viridis', alpha=0.8) + ax.set(**log_kw, title='surface') + + ax = fig.add_subplot(3, 4, 6, projection='3d') + tri_data = _make_triangulation_data() + ax.plot_trisurf(*tri_data, cmap='viridis', alpha=0.8) + ax.set(**log_kw, title='trisurf') + + ax = fig.add_subplot(3, 4, 7, projection='3d') + ax.contour(surf_X, surf_Y, surf_Z, levels=10) + ax.set(**log_kw, title='contour') + + ax = fig.add_subplot(3, 4, 8, projection='3d') + ax.contourf(surf_X, surf_Y, surf_Z, levels=10, alpha=0.8) + ax.set(**log_kw, title='contourf') + + # Row 3: stem, quiver, text + ax = fig.add_subplot(3, 4, 9, projection='3d') + ax.stem([1, 10, 100], [1, 10, 100], [10, 100, 1000], bottom=1) + ax.set(**log_kw, title='stem') + + ax = fig.add_subplot(3, 4, 10, projection='3d') + qxyz = np.array([1, 10, 100]) + ax.quiver(qxyz, qxyz, qxyz, qxyz * 0.5, qxyz * 0.5, qxyz * 0.5) + ax.set(**log_kw, title='quiver') + + ax = fig.add_subplot(3, 4, 11, projection='3d') + ax.text(1, 1, 1, "Point A") + ax.text(10, 10, 10, "Point B") + ax.text(100, 100, 100, "Point C") + ax.set(**log_kw, title='text', + xlim=(0.5, 200), ylim=(0.5, 200), zlim=(0.5, 200)) + + +@mpl3d_image_comparison(['scale3d_all_scales.png'], style='mpl20', remove_text=False) +def test_scale3d_all_scales(): + """Test all scale types with mixed scales on each axis.""" + fig, axs = plt.subplots(1, 2, subplot_kw={'projection': '3d'}, figsize=(10, 6)) + + # Data that works across all scale types + t = np.linspace(0.1, 0.9, 30) + # x: positive for log/asinh, y: spans neg/pos for symlog, z: (0,1) for logit + x = t * 100 # 10 to 90 + y = (t - 0.5) * 20 # -10 to 10 + z = t # 0.1 to 0.9 + + # Subplot 1: x=log, y=symlog, z=logit + axs[0].scatter(x, y, z) + axs[0].set(xscale='log', yscale='symlog', zscale='logit', + xlabel='log', ylabel='symlog', zlabel='logit') + + # Subplot 2: x=asinh, y=linear, z=function (square root) + axs[1].scatter(x, y, z) + axs[1].set_xscale('asinh') + axs[1].set_zscale('function', functions=(lambda v: v**0.5, lambda v: v**2)) + axs[1].set(xlabel='asinh', ylabel='linear', zlabel='function') + + +@pytest.mark.parametrize("scale, expected_lims", [ + ("linear", (-0.020833333333333332, 1.0208333333333333)), + ("log", (0.03640537388223389, 1.1918138759519783)), + ("symlog", (-0.020833333333333332, 1.0208333333333333)), + ("logit", (0.029640777806688817, 0.9703592221933112)), + ("asinh", (-0.020833333333333332, 1.0208333333333333)), +]) +@mpl.style.context("default") +def test_scale3d_default_limits(scale, expected_lims): + """Default axis limits on an empty plot should be correct for each scale.""" + fig = plt.figure() + ax = fig.add_subplot(projection='3d') + ax.set_xscale(scale) + ax.set_yscale(scale) + ax.set_zscale(scale) + fig.canvas.draw() + + for get_lim in (ax.get_xlim, ax.get_ylim, ax.get_zlim): + np.testing.assert_allclose(get_lim(), expected_lims) + + +@check_figures_equal() +@pytest.mark.filterwarnings("ignore:Data has no positive values") +def test_scale3d_all_clipped(fig_test, fig_ref): + """Fully clipped data (e.g. negative values on log) should look like an empty plot. + """ + lims = (0.1, 10) + for ax in [fig_test.add_subplot(projection='3d'), + fig_ref.add_subplot(projection='3d')]: + ax.set_xscale('log') + ax.set_yscale('log') + ax.set_zscale('log') + ax.set(xlim=lims, ylim=lims, zlim=lims) + + # All negative data — everything is invalid for log scale + fig_test.axes[0].plot([-1, -2, -3], [-4, -5, -6], [-7, -8, -9]) + + +@mpl3d_image_comparison(['scale3d_log_bases.png'], style='mpl20', remove_text=False) +def test_scale3d_log_bases(): + """Test log scale with different bases and subs.""" + fig, axs = plt.subplots(2, 2, subplot_kw={'projection': '3d'}, figsize=(10, 8)) + x, y, z = _make_log_data() + + for ax, base, title in [(axs[0, 0], 10, 'base=10'), + (axs[0, 1], 2, 'base=2'), + (axs[1, 0], np.e, 'base=e')]: + ax.scatter(x, y, z, s=10) + ax.set_xscale('log', base=base) + ax.set_yscale('log', base=base) + ax.set_zscale('log', base=base) + ax.set_title(title) + if base == np.e: + # Format tick labels as e^n instead of 2.718...^n + def fmt_e(x, pos=None): + if x <= 0: + return '' + exp = np.log(x) + if np.isclose(exp, round(exp)): + return r'$e^{%d}$' % round(exp) + return '' + ax.xaxis.set_major_formatter(fmt_e) + ax.yaxis.set_major_formatter(fmt_e) + ax.zaxis.set_major_formatter(fmt_e) + + # subs + axs[1, 1].scatter(x, y, z, s=10) + axs[1, 1].set_xscale('log', subs=[2, 5]) + axs[1, 1].set_yscale('log', subs=[2, 5]) + axs[1, 1].set_zscale('log', subs=[2, 5]) + axs[1, 1].set_title('subs=[2,5]') + + +@mpl3d_image_comparison(['scale3d_symlog_params.png'], style='mpl20', + remove_text=False) +def test_scale3d_symlog_params(): + """Test symlog scale with different linthresh values.""" + fig, axs = plt.subplots(1, 2, subplot_kw={'projection': '3d'}) + + # Data spanning negative, zero, and positive + t = np.linspace(-3, 3, 50) + x = np.sinh(t) * 10 + y = t ** 3 + z = np.sign(t) * np.abs(t) ** 2 + + for ax, linthresh in [(axs[0], 0.1), (axs[1], 10)]: + ax.scatter(x, y, z, c=np.abs(z), cmap='viridis', s=10) + ax.set_xscale('symlog', linthresh=linthresh) + ax.set_yscale('symlog', linthresh=linthresh) + ax.set_zscale('symlog', linthresh=linthresh) + ax.set_title(f'linthresh={linthresh}') + + +@pytest.mark.parametrize('scale_type,kwargs', [ + ('log', {'base': 10}), + ('log', {'base': 2}), + ('log', {'subs': [2, 5]}), + ('log', {'nonpositive': 'mask'}), + ('symlog', {'base': 2}), + ('symlog', {'linthresh': 1}), + ('symlog', {'linscale': 0.5}), + ('symlog', {'subs': [2, 5]}), + ('asinh', {'linear_width': 0.5}), + ('asinh', {'base': 2}), + ('logit', {'nonpositive': 'clip'}), +]) +def test_scale3d_keywords_accepted(scale_type, kwargs): + """Verify that scale keywords are accepted on all 3 axes.""" + fig = plt.figure() + ax = fig.add_subplot(projection='3d') + for setter in [ax.set_xscale, ax.set_yscale, ax.set_zscale]: + setter(scale_type, **kwargs) + assert (ax.get_xscale(), ax.get_yscale(), ax.get_zscale()) == (scale_type,) * 3 + + +@pytest.mark.parametrize('axis', ['x', 'y', 'z']) +def test_scale3d_limit_range_log(axis): + """Log scale should warn when setting non-positive limits.""" + fig = plt.figure() + ax = fig.add_subplot(projection='3d') + getattr(ax, f'set_{axis}scale')('log') + + # Setting non-positive limits should warn + with pytest.warns(UserWarning, match="non-positive"): + getattr(ax, f'set_{axis}lim')(-10, 100) + + +def test_scale3d_limit_range_logit(): + """Logit scale should constrain axis to (0, 1).""" + fig = plt.figure() + ax = fig.add_subplot(projection='3d') + ax.set(xscale='logit', yscale='logit', zscale='logit', + xlim=(-0.5, 1.5), ylim=(-0.5, 1.5), zlim=(-0.5, 1.5)) + + # Limits should be constrained to (0, 1) + for name, lim in [('x', ax.get_xlim()), ('y', ax.get_ylim()), + ('z', ax.get_zlim())]: + assert lim[0] > 0, f"{name} lower limit should be > 0 for logit" + assert lim[1] < 1, f"{name} upper limit should be < 1 for logit" + + +@pytest.mark.parametrize('scale_type', ['log', 'symlog', 'logit', 'asinh']) +def test_scale3d_transform_roundtrip(scale_type): + """Forward/inverse transform should preserve values.""" + fig = plt.figure() + ax = fig.add_subplot(projection='3d') + ax.set(xscale=scale_type, yscale=scale_type, zscale=scale_type) + + # Use appropriate test values for each scale type + test_values = { + 'log': [1, 10, 100, 1000], + 'symlog': [-100, -1, 0, 1, 100], + 'asinh': [-100, -1, 0, 1, 100], + 'logit': [0.01, 0.1, 0.5, 0.9, 0.99], + }[scale_type] + test_values = np.array(test_values) + + # Test round-trip for each axis + for axis in [ax.xaxis, ax.yaxis, ax.zaxis]: + trans = axis.get_transform() + forward = trans.transform(test_values.reshape(-1, 1)) + inverse = trans.inverted().transform(forward) + np.testing.assert_allclose(inverse.flatten(), test_values, rtol=1e-10) + + +def test_scale3d_invalid_keywords_raise(): + """Invalid kwargs should raise TypeError.""" + fig = plt.figure() + ax = fig.add_subplot(projection='3d') + + with pytest.raises(TypeError): + ax.set_xscale('log', invalid_kwarg=True) + + with pytest.raises(TypeError): + ax.set_yscale('symlog', invalid_kwarg=True) + + with pytest.raises(TypeError): + ax.set_zscale('logit', invalid_kwarg=True) + + +def test_scale3d_persists_after_plot(): + """Scale should persist after adding plot data.""" + fig = plt.figure() + ax = fig.add_subplot(projection='3d') + ax.set(xscale='log', yscale='log', zscale='log') + ax.plot(*_make_log_data()) + assert (ax.get_xscale(), ax.get_yscale(), ax.get_zscale()) == ('log',) * 3 + + +def test_scale3d_autoscale_with_log(): + """Autoscale should work correctly with log scale.""" + fig = plt.figure() + ax = fig.add_subplot(projection='3d') + ax.set(xscale='log', yscale='log', zscale='log') + ax.scatter([1, 10, 100], [1, 10, 100], [1, 10, 100]) + + # All limits should be positive + for name, lim in [('x', ax.get_xlim()), ('y', ax.get_ylim()), + ('z', ax.get_zlim())]: + assert lim[0] > 0, f"{name} lower limit should be positive" + assert lim[1] > 0, f"{name} upper limit should be positive" + + +@pytest.mark.parametrize("method", ["semilogx", "semilogy", "semilogz", "loglog"]) +def test_semilog_loglog_not_implemented(method): + """semilogx/y/z and loglog should raise NotImplementedError on Axes3D.""" + fig = plt.figure() + ax = fig.add_subplot(projection='3d') + with pytest.raises(NotImplementedError, match="Axes3D does not support"): + getattr(ax, method)([1, 10, 100], [1, 2, 3]) + + +def test_scale3d_calc_coord(): + """_calc_coord should return data coordinates with correct pane values.""" + fig = plt.figure() + ax = fig.add_subplot(projection='3d') + ax.scatter([1, 10, 100], [1, 10, 100], [1, 10, 100]) + ax.set(xscale='log', yscale='log', zscale='log') + fig.canvas.draw() + + point, pane_idx = ax._calc_coord(0.5, 0.5) + # Pane coordinate should match axis limit (y-pane at max) + assert pane_idx == 1 + assert point[pane_idx] == pytest.approx(ax.get_ylim()[1]) diff --git a/lib/mpl_toolkits/mplot3d/tests/test_legend3d.py b/lib/mpl_toolkits/mplot3d/tests/test_legend3d.py index 0935bbe7f6b0..9ca048e18ba9 100644 --- a/lib/mpl_toolkits/mplot3d/tests/test_legend3d.py +++ b/lib/mpl_toolkits/mplot3d/tests/test_legend3d.py @@ -28,7 +28,7 @@ def test_legend_bar(): @image_comparison(['fancy.png'], remove_text=True, style='mpl20', - tol=0.011 if platform.machine() == 'arm64' else 0) + 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') @@ -47,9 +47,9 @@ def test_linecollection_scaled_dashes(): 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) + ax.add_collection(lc1, autolim="_datalim_only") + ax.add_collection(lc2, autolim="_datalim_only") + ax.add_collection(lc3, autolim="_datalim_only") leg = ax.legend([lc1, lc2, lc3], ['line1', 'line2', 'line 3']) h1, h2, h3 = leg.legend_handles @@ -90,8 +90,7 @@ def test_contourf_legend_elements(): 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.cmap = cs.cmap.with_extremes(over='red', under='blue') cs.changed() artists, labels = cs.legend_elements() assert labels == ['$x \\leq -1e+250s$', diff --git a/meson.build b/meson.build index a50f0b8f743a..47244656705f 100644 --- a/meson.build +++ b/meson.build @@ -1,7 +1,10 @@ project( 'matplotlib', 'c', 'cpp', - version: run_command(find_program('python3'), '-m', 'setuptools_scm', check: true).stdout().strip(), + 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 @@ -28,6 +31,10 @@ project( ], ) +# Enable bug fixes in Agg +add_project_arguments('-DMPL_FIX_AGG_IMAGE_FILTER_LUT_BUGS', language : 'cpp') +add_project_arguments('-DMPL_FIX_AGG_INTERPOLATION_ENDPOINT_BUG', language : 'cpp') + cc = meson.get_compiler('c') cpp = meson.get_compiler('cpp') diff --git a/pyproject.toml b/pyproject.toml index 48a174731440..112528dc9d5d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,10 +16,10 @@ classifiers=[ "License :: OSI Approved :: Python Software Foundation License", "Programming Language :: Python", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", "Topic :: Scientific/Engineering :: Visualization", ] @@ -35,23 +35,24 @@ dependencies = [ "cycler >= 0.10", "fonttools >= 4.22.0", "kiwisolver >= 1.3.1", - "numpy >= 1.23", + "numpy >= 1.25", "packaging >= 20.0", - "pillow >= 8", - "pyparsing >= 2.3.1", + "pillow >= 9", + "pyparsing >= 3", "python-dateutil >= 2.7", ] -requires-python = ">=3.10" +# 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", + "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-instaled dependency, so + # 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", @@ -70,7 +71,9 @@ dev = [ build-backend = "mesonpy" # Also keep in sync with optional dependencies above. requires = [ - "meson-python>=0.13.1", + # 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", ] @@ -89,19 +92,16 @@ known_pydata = "numpy, matplotlib.pyplot" known_firstparty = "matplotlib,mpl_toolkits" sections = "FUTURE,STDLIB,THIRDPARTY,PYDATA,FIRSTPARTY,LOCALFOLDER" force_sort_within_sections = true +line_length = 88 [tool.ruff] -exclude = [ - ".git", +extend-exclude = [ "build", "doc/gallery", "doc/tutorials", "tools/gh_api.py", - ".tox", - ".eggs", ] line-length = 88 -target-version = "py310" [tool.ruff.lint] ignore = [ @@ -112,15 +112,26 @@ ignore = [ "D104", "D105", "D106", + "D107", "D200", "D202", + "D203", "D204", "D205", + "D212", "D301", "D400", "D401", + "D402", "D403", "D404", + "D413", + "D415", + "D417", + "E266", + "E305", + "E306", + "E721", "E741", "F841", ] @@ -131,6 +142,7 @@ select = [ "E", "F", "W", + "UP035", # The following error codes require the preview mode to be enabled. "E201", "E202", @@ -139,6 +151,7 @@ select = [ "E251", "E261", "E272", + "E302", "E703", ] @@ -149,22 +162,20 @@ select = [ # See https://github.com/charliermarsh/ruff/issues/2402 for status on implementation external = [ "E122", - "E302", ] [tool.ruff.lint.pydocstyle] convention = "numpy" [tool.ruff.lint.per-file-ignores] +"*.pyi" = ["E501"] +"*.ipynb" = ["E402"] "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"] @@ -178,9 +189,9 @@ convention = "numpy" "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/_cm.py" = ["E202", "E203"] +"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"] @@ -193,20 +204,17 @@ convention = "numpy" "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/artists/transforms_tutorial.py" = ["E402"] "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"] @@ -217,24 +225,20 @@ enable_error_code = [ "redundant-expr", "truthy-bool", ] -enable_incomplete_feature = [ - "Unpack", -] exclude = [ #stubtest - ".*/matplotlib/(sphinxext|backends|testing/jpl_units)", + ".*/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" + ".*/tinypages", + # pylab's numpy wildcard imports cause re-def failures since numpy 2.2 + "lib/matplotlib/pylab.py", ] files = [ "lib/matplotlib", @@ -318,3 +322,23 @@ testpaths = ["lib"] addopts = [ "--import-mode=importlib", ] + +[tool.cibuildwheel.pyodide] +test-requires = "pytest" +test-command = [ + # Wheels are built without test images, so copy them into the testing directory. + "basedir=$(python -c 'import pathlib, matplotlib; print(pathlib.Path(matplotlib.__file__).parent.parent)')", + "cp -a {package}/lib/matplotlib/tests/data $basedir/matplotlib/tests/", + """ + for subdir in matplotlib mpl_toolkits/axes_grid1 mpl_toolkits/axisartist mpl_toolkits/mplot3d; do + cp -a {package}/lib/${subdir}/tests/baseline_images $basedir/${subdir}/tests/ + done""", + # Test installed, not repository, copy as we aren't using an editable install. + "pytest -p no:cacheprovider --pyargs matplotlib mpl_toolkits.axes_grid1 mpl_toolkits.axisartist mpl_toolkits.mplot3d", +] +[tool.cibuildwheel.pyodide.environment] +# Exceptions are needed for pybind11: +# https://github.com/pybind/pybind11/pull/5298 +CFLAGS = "-fexceptions" +CXXFLAGS = "-fexceptions" +LDFLAGS = "-fexceptions" diff --git a/requirements/dev/dev-requirements.txt b/requirements/dev/dev-requirements.txt index e5cbc1091bb2..3208949ba0e8 100644 --- a/requirements/dev/dev-requirements.txt +++ b/requirements/dev/dev-requirements.txt @@ -2,4 +2,4 @@ -r ../doc/doc-requirements.txt -r ../testing/all.txt -r ../testing/extra.txt --r ../testing/flake8.txt +ruff diff --git a/requirements/doc/doc-requirements.txt b/requirements/doc/doc-requirements.txt index 77cb606130b0..1a352eaae975 100644 --- a/requirements/doc/doc-requirements.txt +++ b/requirements/doc/doc-requirements.txt @@ -14,8 +14,7 @@ ipywidgets ipykernel numpydoc>=1.0 packaging>=20 -pydata-sphinx-theme~=0.15.0 -mpl-sphinx-theme~=3.9.0 +mpl-sphinx-theme~=3.10.0 pyyaml PyStemmer sphinxcontrib-svg2pdfconverter>=1.1.0 diff --git a/requirements/testing/all.txt b/requirements/testing/all.txt index e386924a9b67..dd1dbf3f29fd 100644 --- a/requirements/testing/all.txt +++ b/requirements/testing/all.txt @@ -1,12 +1,12 @@ # pip requirements for all the CI builds -black<24 +black<26 certifi coverage!=6.3 psutil pytest!=4.6.0,!=5.4.0,!=8.1.0 pytest-cov -pytest-rerunfailures +pytest-rerunfailures!=16.0 pytest-timeout pytest-xdist pytest-xvfb diff --git a/requirements/testing/extra.txt b/requirements/testing/extra.txt index a5c1bef5f03a..e0d84d71c781 100644 --- a/requirements/testing/extra.txt +++ b/requirements/testing/extra.txt @@ -1,4 +1,4 @@ -# Extra pip requirements for the Python 3.10+ builds +# Extra pip requirements --prefer-binary ipykernel diff --git a/requirements/testing/flake8.txt b/requirements/testing/flake8.txt deleted file mode 100644 index a4d006b8551e..000000000000 --- a/requirements/testing/flake8.txt +++ /dev/null @@ -1,9 +0,0 @@ -# Extra pip requirements for the GitHub Actions flake8 build - -flake8>=3.8 -# versions less than 5.1.0 raise on some interp'd docstrings -pydocstyle>=5.1.0 -# 1.4.0 adds docstring-convention=all -flake8-docstrings>=1.4.0 -# fix bug where flake8 aborts checking on syntax error -flake8-force diff --git a/requirements/testing/minver.txt b/requirements/testing/minver.txt index 3932e68eb015..ee55f6c7b1bf 100644 --- a/requirements/testing/minver.txt +++ b/requirements/testing/minver.txt @@ -7,9 +7,17 @@ importlib-resources==3.2.0 kiwisolver==1.3.2 meson-python==0.13.1 meson==1.1.0 -numpy==1.23.0 +numpy==1.25.0 packaging==20.0 -pillow==8.3.2 -pyparsing==2.3.1 +pillow==9.0.1 +pyparsing==3.0.0 pytest==7.0.0 python-dateutil==2.7 + +# 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 index aa20581ee69b..343517263f40 100644 --- a/requirements/testing/mypy.txt +++ b/requirements/testing/mypy.txt @@ -19,8 +19,8 @@ cycler>=0.10 fonttools>=4.22.0 kiwisolver>=1.3.1 packaging>=20.0 -pillow>=8 -pyparsing>=2.3.1 +pillow>=9 +pyparsing>=3 python-dateutil>=2.7 setuptools_scm>=7 setuptools>=64 diff --git a/src/_backend_agg.cpp b/src/_backend_agg.cpp index eed27323ba9e..4d097bc80716 100644 --- a/src/_backend_agg.cpp +++ b/src/_backend_agg.cpp @@ -10,9 +10,9 @@ RendererAgg::RendererAgg(unsigned int width, unsigned int height, double dpi) height(height), dpi(dpi), NUMBYTES((size_t)width * (size_t)height * 4), - pixBuffer(NULL), + pixBuffer(nullptr), renderingBuffer(), - alphaBuffer(NULL), + alphaBuffer(nullptr), alphaMaskRenderingBuffer(), alphaMask(alphaMaskRenderingBuffer), pixfmtAlphaMask(alphaMaskRenderingBuffer), @@ -26,7 +26,7 @@ RendererAgg::RendererAgg(unsigned int width, unsigned int height, double dpi) rendererAA(), rendererBin(), theRasterizer(32768), - lastclippath(NULL), + lastclippath(nullptr), _fill_color(agg::rgba(1, 1, 1, 0)) { if (dpi <= 0.0) { @@ -75,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; @@ -90,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"); } diff --git a/src/_backend_agg.h b/src/_backend_agg.h index 8010508ae920..1ac3d4c06b13 100644 --- a/src/_backend_agg.h +++ b/src/_backend_agg.h @@ -11,6 +11,8 @@ #include #include #include +#include +#include #include "agg_alpha_mask_u8.h" #include "agg_conv_curve.h" @@ -65,6 +67,10 @@ class BufferRegion delete[] data; }; + // prevent copying + BufferRegion(const BufferRegion &) = delete; + BufferRegion &operator=(const BufferRegion &) = delete; + agg::int8u *get_data() { return data; @@ -96,15 +102,8 @@ class BufferRegion int width; int height; int stride; - - private: - // prevent copying - BufferRegion(const BufferRegion &); - BufferRegion &operator=(const BufferRegion &); }; -#define MARKER_CACHE_SIZE 512 - // the renderer class RendererAgg { @@ -125,9 +124,6 @@ class RendererAgg typedef agg::renderer_base renderer_base_alpha_mask_type; typedef agg::renderer_scanline_aa_solid renderer_alpha_mask_type; - /* TODO: Remove facepair_t */ - typedef std::pair facepair_t; - RendererAgg(unsigned int width, unsigned int height, double dpi); virtual ~RendererAgg(); @@ -178,7 +174,8 @@ class RendererAgg ColorArray &edgecolors, LineWidthArray &linewidths, DashesVector &linestyles, - AntialiasedArray &antialiaseds); + AntialiasedArray &antialiaseds, + ColorArray &hatchcolors); template void draw_quad_mesh(GCAgg &gc, @@ -249,7 +246,7 @@ class RendererAgg 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); + void _draw_path(PathIteratorType &path, bool has_clippath, const std::optional &face, GCAgg &gc); template void _draw_gouraud_triangle(PointArray &points, @@ -295,7 +293,7 @@ class RendererAgg template inline void -RendererAgg::_draw_path(path_t &path, bool has_clippath, const facepair_t &face, GCAgg &gc) +RendererAgg::_draw_path(path_t &path, bool has_clippath, const std::optional &face, GCAgg &gc) { typedef agg::conv_stroke stroke_t; typedef agg::conv_dash dash_t; @@ -306,7 +304,7 @@ RendererAgg::_draw_path(path_t &path, bool has_clippath, const facepair_t &face, typedef agg::renderer_scanline_bin_solid amask_bin_renderer_type; // Render face - if (face.first) { + if (face) { theRasterizer.add_path(path); if (gc.isaa) { @@ -314,10 +312,10 @@ RendererAgg::_draw_path(path_t &path, bool has_clippath, const facepair_t &face, pixfmt_amask_type pfa(pixFmt, alphaMask); amask_ren_type r(pfa); amask_aa_renderer_type ren(r); - ren.color(face.second); + ren.color(*face); agg::render_scanlines(theRasterizer, scanlineAlphaMask, ren); } else { - rendererAA.color(face.second); + rendererAA.color(*face); agg::render_scanlines(theRasterizer, slineP8, rendererAA); } } else { @@ -325,10 +323,10 @@ RendererAgg::_draw_path(path_t &path, bool has_clippath, const facepair_t &face, pixfmt_amask_type pfa(pixFmt, alphaMask); amask_ren_type r(pfa); amask_bin_renderer_type ren(r); - ren.color(face.second); + ren.color(*face); agg::render_scanlines(theRasterizer, scanlineAlphaMask, ren); } else { - rendererBin.color(face.second); + rendererBin.color(*face); agg::render_scanlines(theRasterizer, slineP8, rendererBin); } } @@ -458,7 +456,10 @@ RendererAgg::draw_path(GCAgg &gc, PathIterator &path, agg::trans_affine &trans, typedef agg::conv_curve curve_t; typedef Sketch sketch_t; - facepair_t face(color.a != 0.0, color); + std::optional face; + if (color.a != 0.0) { + face = color; + } theRasterizer.reset_clipping(); rendererBase.reset_clipping(true); @@ -467,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(); + bool clip = !face && !gc.has_hatchpath(); bool simplify = path.should_simplify() && clip; double snapping_linewidth = points_to_pixels(gc.linewidth); if (gc.color.a == 0.0) { @@ -529,7 +530,10 @@ inline void RendererAgg::draw_markers(GCAgg &gc, curve_t path_curve(path_snapped); path_curve.rewind(0); - facepair_t face(color.a != 0.0, color); + std::optional face; + if (color.a != 0.0) { + face = color; + } // maxim's suggestions for cached scanlines agg::scanline_storage_aa8 scanlines; @@ -538,22 +542,14 @@ inline void RendererAgg::draw_markers(GCAgg &gc, rendererBase.reset_clipping(true); agg::rect_i marker_size(0x7FFFFFFF, 0x7FFFFFFF, -0x7FFFFFFF, -0x7FFFFFFF); - agg::int8u staticFillCache[MARKER_CACHE_SIZE]; - agg::int8u staticStrokeCache[MARKER_CACHE_SIZE]; - agg::int8u *fillCache = staticFillCache; - agg::int8u *strokeCache = staticStrokeCache; - try { - unsigned fillSize = 0; - if (face.first) { + std::vector fillBuffer; + if (face) { theRasterizer.add_path(marker_path_curve); agg::render_scanlines(theRasterizer, slineP8, scanlines); - fillSize = scanlines.byte_size(); - if (fillSize >= MARKER_CACHE_SIZE) { - fillCache = new agg::int8u[fillSize]; - } - scanlines.serialize(fillCache); + fillBuffer.resize(scanlines.byte_size()); + scanlines.serialize(fillBuffer.data()); marker_size = agg::rect_i(scanlines.min_x(), scanlines.min_y(), scanlines.max_x(), @@ -568,11 +564,8 @@ inline void RendererAgg::draw_markers(GCAgg &gc, theRasterizer.reset(); theRasterizer.add_path(stroke); agg::render_scanlines(theRasterizer, slineP8, scanlines); - unsigned strokeSize = scanlines.byte_size(); - if (strokeSize >= MARKER_CACHE_SIZE) { - strokeCache = new agg::int8u[strokeSize]; - } - scanlines.serialize(strokeCache); + std::vector strokeBuffer(scanlines.byte_size()); + scanlines.serialize(strokeBuffer.data()); marker_size = agg::rect_i(std::min(marker_size.x1, scanlines.min_x()), std::min(marker_size.y1, scanlines.min_y()), std::max(marker_size.x2, scanlines.max_x()), @@ -616,13 +609,13 @@ inline void RendererAgg::draw_markers(GCAgg &gc, amask_ren_type r(pfa); amask_aa_renderer_type ren(r); - if (face.first) { - ren.color(face.second); - sa.init(fillCache, fillSize, x, y); + if (face) { + ren.color(*face); + sa.init(fillBuffer.data(), fillBuffer.size(), x, y); agg::render_scanlines(sa, sl, ren); } ren.color(gc.color); - sa.init(strokeCache, strokeSize, x, y); + sa.init(strokeBuffer.data(), strokeBuffer.size(), x, y); agg::render_scanlines(sa, sl, ren); } } else { @@ -644,34 +637,25 @@ inline void RendererAgg::draw_markers(GCAgg &gc, continue; } - if (face.first) { - rendererAA.color(face.second); - sa.init(fillCache, fillSize, x, y); + if (face) { + rendererAA.color(*face); + sa.init(fillBuffer.data(), fillBuffer.size(), x, y); agg::render_scanlines(sa, sl, rendererAA); } rendererAA.color(gc.color); - sa.init(strokeCache, strokeSize, x, y); + sa.init(strokeBuffer.data(), strokeBuffer.size(), x, y); agg::render_scanlines(sa, sl, rendererAA); } } } catch (...) { - if (fillCache != staticFillCache) - delete[] fillCache; - if (strokeCache != staticStrokeCache) - delete[] strokeCache; theRasterizer.reset_clipping(); rendererBase.reset_clipping(true); throw; } - if (fillCache != staticFillCache) - delete[] fillCache; - if (strokeCache != staticStrokeCache) - delete[] strokeCache; - theRasterizer.reset_clipping(); rendererBase.reset_clipping(true); } @@ -890,7 +874,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.shape(0))), (agg::int8u)(alpha * 255)); + pixf, nullptr, (int)x, (int)(height - (y + image.shape(0))), (agg::int8u)(alpha * 255)); } rendererBase.reset_clipping(true); @@ -918,7 +902,8 @@ inline void RendererAgg::_draw_path_collection_generic(GCAgg &gc, DashesVector &linestyles, AntialiasedArray &antialiaseds, bool check_snap, - bool has_codes) + bool has_codes, + ColorArray &hatchcolors) { typedef agg::conv_transform transformed_path_t; typedef PathNanRemover nan_removed_t; @@ -938,11 +923,12 @@ inline void RendererAgg::_draw_path_collection_generic(GCAgg &gc, 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 = safe_first_shape(antialiaseds); - if ((Nfacecolors == 0 && Nedgecolors == 0) || Npaths == 0) { + if ((Nfacecolors == 0 && Nedgecolors == 0 && Nhatchcolors == 0) || Npaths == 0) { return; } @@ -954,10 +940,9 @@ inline void RendererAgg::_draw_path_collection_generic(GCAgg &gc, // Set some defaults, assuming no face or edge gc.linewidth = 0.0; - facepair_t face; - face.first = Nfacecolors != 0; + std::optional face; agg::trans_affine trans; - bool do_clip = !face.first && !gc.has_hatchpath(); + bool do_clip = Nfacecolors == 0 && !gc.has_hatchpath(); for (int i = 0; i < (int)N; ++i) { typename PathGenerator::path_iterator path = path_generator(i); @@ -988,7 +973,7 @@ inline void RendererAgg::_draw_path_collection_generic(GCAgg &gc, if (Nfacecolors) { int ic = i % Nfacecolors; - face.second = agg::rgba(facecolors(ic, 0), facecolors(ic, 1), facecolors(ic, 2), facecolors(ic, 3)); + face.emplace(facecolors(ic, 0), facecolors(ic, 1), facecolors(ic, 2), facecolors(ic, 3)); } if (Nedgecolors) { @@ -1005,6 +990,11 @@ inline void RendererAgg::_draw_path_collection_generic(GCAgg &gc, } } + 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); @@ -1049,7 +1039,8 @@ inline void RendererAgg::draw_path_collection(GCAgg &gc, ColorArray &edgecolors, LineWidthArray &linewidths, DashesVector &linestyles, - AntialiasedArray &antialiaseds) + AntialiasedArray &antialiaseds, + ColorArray &hatchcolors) { _draw_path_collection_generic(gc, master_transform, @@ -1066,7 +1057,8 @@ inline void RendererAgg::draw_path_collection(GCAgg &gc, linestyles, antialiaseds, true, - true); + true, + hatchcolors); } template @@ -1160,6 +1152,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, @@ -1176,7 +1169,8 @@ inline void RendererAgg::draw_quad_mesh(GCAgg &gc, linestyles, antialiaseds, true, // check_snap - false); + false, + hatchcolors); } template diff --git a/src/_backend_agg_basic_types.h b/src/_backend_agg_basic_types.h index e3e6be9a4532..b424419ec99e 100644 --- a/src/_backend_agg_basic_types.h +++ b/src/_backend_agg_basic_types.h @@ -48,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 { @@ -59,9 +59,7 @@ class Dashes void dash_to_stroke(T &stroke, double dpi, bool isaa) { double scaleddpi = dpi / 72.0; - for (dash_t::const_iterator i = dashes.begin(); i != dashes.end(); ++i) { - double val0 = i->first; - double val1 = i->second; + for (auto [val0, val1] : dashes) { val0 = val0 * scaleddpi; val1 = val1 * scaleddpi; if (!isaa) { diff --git a/src/_backend_agg_wrapper.cpp b/src/_backend_agg_wrapper.cpp index 269e2aaa9ee5..11d45773d186 100644 --- a/src/_backend_agg_wrapper.cpp +++ b/src/_backend_agg_wrapper.cpp @@ -146,12 +146,14 @@ PyRendererAgg_draw_path_collection(RendererAgg *self, 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::object Py_UNUSED(offset_position_obj), + py::array_t hatchcolors_obj) { 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>(); @@ -165,7 +167,8 @@ PyRendererAgg_draw_path_collection(RendererAgg *self, edgecolors, linewidths, dashes, - antialiaseds); + antialiaseds, + hatchcolors); } static void @@ -229,7 +232,8 @@ PYBIND11_MODULE(_backend_agg, m, py::mod_gil_not_used()) .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) + "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, @@ -250,12 +254,12 @@ PYBIND11_MODULE(_backend_agg, m, py::mod_gil_not_used()) .def_buffer([](RendererAgg *renderer) -> py::buffer_info { std::vector shape { - renderer->get_height(), - renderer->get_width(), + static_cast(renderer->get_height()), + static_cast(renderer->get_width()), 4 }; std::vector strides { - renderer->get_width() * 4, + static_cast(renderer->get_width() * 4), 4, 1 }; diff --git a/src/_c_internal_utils.cpp b/src/_c_internal_utils.cpp index db6191849bbe..31eb92444862 100644 --- a/src/_c_internal_utils.cpp +++ b/src/_c_internal_utils.cpp @@ -41,13 +41,13 @@ mpl_xdisplay_is_valid(void) // 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 = NULL; - XOpenDisplay_t XOpenDisplay = (XOpenDisplay_t)dlsym(libX11, "XOpenDisplay"); - XCloseDisplay_t XCloseDisplay = (XCloseDisplay_t)dlsym(libX11, "XCloseDisplay"); + struct Display* display = nullptr; + auto XOpenDisplay = (struct Display* (*)(char const*)) + dlsym(libX11, "XOpenDisplay"); + auto XCloseDisplay = (int (*)(struct Display*)) + dlsym(libX11, "XCloseDisplay"); if (XOpenDisplay && XCloseDisplay - && (display = XOpenDisplay(NULL))) { + && (display = XOpenDisplay(nullptr))) { XCloseDisplay(display); } if (dlclose(libX11)) { @@ -73,15 +73,13 @@ mpl_display_is_valid(void) 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 = NULL; - 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"); + struct wl_display* display = nullptr; + auto wl_display_connect = (struct wl_display* (*)(char const*)) + dlsym(libwayland_client, "wl_display_connect"); + auto wl_display_disconnect = (void (*)(struct wl_display*)) + dlsym(libwayland_client, "wl_display_disconnect"); if (wl_display_connect && wl_display_disconnect - && (display = wl_display_connect(NULL))) { + && (display = wl_display_connect(nullptr))) { wl_display_disconnect(display); } if (dlclose(libwayland_client)) { @@ -162,25 +160,19 @@ mpl_SetProcessDpiAwareness_max(void) #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"); + auto IsValidDpiAwarenessContext = (BOOL (WINAPI *)(DPI_AWARENESS_CONTEXT)) + GetProcAddress(user32, "IsValidDpiAwarenessContext"); + auto SetProcessDpiAwarenessContext = (BOOL (WINAPI *)(DPI_AWARENESS_CONTEXT)) + 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) { + if (IsValidDpiAwarenessContext && SetProcessDpiAwarenessContext) { for (size_t i = 0; i < sizeof(ctxs) / sizeof(DPI_AWARENESS_CONTEXT); ++i) { - if (IsValidDpiAwarenessContextPtr(ctxs[i])) { - SetProcessDpiAwarenessContextPtr(ctxs[i]); + if (IsValidDpiAwarenessContext(ctxs[i])) { + SetProcessDpiAwarenessContext(ctxs[i]); break; } } diff --git a/src/_image_resample.h b/src/_image_resample.h index 282bf8ef82f6..eaaf2306ae9f 100644 --- a/src/_image_resample.h +++ b/src/_image_resample.h @@ -60,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) @@ -246,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) : @@ -500,54 +496,44 @@ typedef enum { } interpolation_e; -// T is rgba if and only if it has an T::r field. +// T is rgba if and only if it has a 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 type_mapping { - using blender_type = typename std::conditional< - is_grayscale::value, + using blender_type = std::conditional_t< + is_grayscale_v, agg::blender_gray, - typename std::conditional< - std::is_same::value, + std::conditional_t< + std::is_same_v, fixed_blender_rgba_plain, - agg::blender_rgba_plain - >::type - >::type; - using pixfmt_type = typename std::conditional< - is_grayscale::value, + agg::blender_rgba_pre + > + >; + using pixfmt_type = std::conditional_t< + is_grayscale_v, agg::pixfmt_alpha_blend_gray, agg::pixfmt_alpha_blend_rgba - >::type; - using pixfmt_pre_type = typename std::conditional< - is_grayscale::value, - pixfmt_type, - agg::pixfmt_alpha_blend_rgba< - typename std::conditional< - std::is_same::value, - fixed_blender_rgba_pre, - agg::blender_rgba_pre - >::type, - agg::rendering_buffer> - >::type; - template using span_gen_affine_type = typename std::conditional< - is_grayscale::value, + >; + template using span_gen_affine_type = std::conditional_t< + is_grayscale_v, agg::span_image_resample_gray_affine, agg::span_image_resample_rgba_affine - >::type; - template using span_gen_filter_type = typename std::conditional< - is_grayscale::value, + >; + template using span_gen_filter_type = std::conditional_t< + is_grayscale_v, agg::span_image_filter_gray, agg::span_image_filter_rgba - >::type; - template using span_gen_nn_type = typename std::conditional< - is_grayscale::value, + >; + template using span_gen_nn_type = std::conditional_t< + is_grayscale_v, agg::span_image_filter_gray_nn, agg::span_image_filter_rgba_nn - >::type; + >; }; @@ -583,23 +569,29 @@ class lookup_distortion { public: lookup_distortion(const double *mesh, int in_width, int in_height, - int out_width, int out_height) : + int out_width, int out_height, bool edge_aligned_subpixels) : m_mesh(mesh), m_in_width(in_width), m_in_height(in_height), m_out_width(out_width), - m_out_height(out_height) + m_out_height(out_height), + m_edge_aligned_subpixels(edge_aligned_subpixels) {} void calculate(int* x, int* y) { if (m_mesh) { + // Nearest-neighbor interpolation needs edge-aligned subpixels + // All other interpolation approaches need center-aligned subpixels + double offset = m_edge_aligned_subpixels ? 0 : 0.5; + double dx = double(*x) / agg::image_subpixel_scale; double dy = double(*y) / agg::image_subpixel_scale; if (dx >= 0 && dx < m_out_width && dy >= 0 && dy < m_out_height) { const double *coord = m_mesh + (int(dy) * m_out_width + int(dx)) * 2; - *x = int(coord[0] * agg::image_subpixel_scale); - *y = int(coord[1] * agg::image_subpixel_scale); + // Add a tiny fudge amount to account for numerical precision loss + *x = int(coord[0] * agg::image_subpixel_scale + offset + 1e-8); + *y = int(coord[1] * agg::image_subpixel_scale + offset + 1e-8); } } } @@ -610,6 +602,7 @@ class lookup_distortion int m_in_height; int m_out_width; int m_out_height; + bool m_edge_aligned_subpixels; }; @@ -716,11 +709,13 @@ void resample( using scanline_t = agg::scanline32_u8; using reflect_t = agg::wrap_mode_reflect; - using image_accessor_t = agg::image_accessor_wrap; + using image_accessor_wrap_t = agg::image_accessor_wrap; + using image_accessor_clip_t = agg::image_accessor_clip; using span_alloc_t = agg::span_allocator; using span_conv_alpha_t = span_conv_alpha; + using nn_affine_interpolator_t = accurate_interpolator_affine_nn<>; using affine_interpolator_t = agg::span_interpolator_linear<>; using arbitrary_interpolator_t = agg::span_interpolator_adaptor, lookup_distortion>; @@ -749,7 +744,8 @@ void resample( 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); + image_accessor_wrap_t input_accessor_wrap(input_pixfmt); + image_accessor_clip_t input_accessor_clip(input_pixfmt, color_type::no_color()); agg::rendering_buffer output_buffer; output_buffer.attach( @@ -763,14 +759,44 @@ void resample( rasterizer.clip_box(0, 0, out_width, out_height); agg::path_storage path; - if (params.is_affine) { - path.move_to(0, 0); - path.line_to(in_width, 0); - path.line_to(in_width, in_height); - path.line_to(0, in_height); - path.close_polygon(); - agg::conv_transform rectangle(path, params.affine); - rasterizer.add_path(rectangle); + if (params.is_affine && params.interpolation != NEAREST) { + if (params.affine.shx != 0 || params.affine.shy != 0) { + path.move_to(0, 0); + path.line_to(in_width, 0); + path.line_to(in_width, in_height); + path.line_to(0, in_height); + path.close_polygon(); + agg::conv_transform rectangle(path, params.affine); + rasterizer.add_path(rectangle); + } else { + // If there is no shear/rotation, bump out the rendering edges that are + // within a half pixel of a full pixel so that axes are visually filled. + // This bumping out is equivalent to treating any edge pixel that is at + // least half-covered by the source as fully covered by the source. + double left = 0; + double right = in_width; + double bottom = 0; + double top = in_height; + params.affine.transform(&left, &bottom); + params.affine.transform(&right, &top); + if (left > right) { std::swap(left, right); } + if (bottom > top) { std::swap(top, bottom); } + // Add a tiny fudge amount to account for numerical precision loss + int rleft = agg::iround(left - 1e-8); + int rright = agg::iround(right + 1e-8); + int rbottom = agg::iround(bottom - 1e-8); + int rtop = agg::iround(top + 1e-8); + if (rleft < left) { left = rleft; } + if (rright > right) { right = rright; } + if (rbottom < bottom) { bottom = rbottom; } + if (rtop > top) { top = rtop; } + path.move_to(left, bottom); + path.line_to(right, bottom); + path.line_to(right, top); + path.line_to(left, top); + path.close_polygon(); + rasterizer.add_path(path); + } } else { path.move_to(0, 0); path.line_to(out_width, 0); @@ -782,22 +808,22 @@ void resample( if (params.interpolation == NEAREST) { if (params.is_affine) { - using span_gen_t = typename type_mapping_t::template span_gen_nn_type; + 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); + nn_affine_interpolator_t interpolator(inverted); + span_gen_t span_gen(input_accessor_clip, 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 { - using span_gen_t = typename type_mapping_t::template span_gen_nn_type; + 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); + params.transform_mesh, in_width, in_height, out_width, out_height, true); arbitrary_interpolator_t interpolator(inverted, dist); - span_gen_t span_gen(input_accessor, interpolator); + span_gen_t span_gen(input_accessor_clip, 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); @@ -807,22 +833,22 @@ void resample( get_filter(params, filter); if (params.is_affine && params.resample) { - using span_gen_t = typename type_mapping_t::template span_gen_affine_type; + 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_gen_t span_gen(input_accessor_wrap, 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 { - using span_gen_t = typename type_mapping_t::template span_gen_filter_type; + 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); + params.transform_mesh, in_width, in_height, out_width, out_height, false); arbitrary_interpolator_t interpolator(inverted, dist); - span_gen_t span_gen(input_accessor, interpolator, filter); + span_gen_t span_gen(input_accessor_wrap, 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); diff --git a/src/_image_wrapper.cpp b/src/_image_wrapper.cpp index 0f7b0da88de8..8944a2d44041 100644 --- a/src/_image_wrapper.cpp +++ b/src/_image_wrapper.cpp @@ -1,6 +1,8 @@ #include #include +#include + #include "_image_resample.h" #include "py_converters.h" @@ -54,7 +56,7 @@ _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. */ // If attribute doesn't exist, raises Python AttributeError @@ -66,8 +68,10 @@ _get_transform_mesh(const py::object& transform, const py::ssize_t *dims) for (auto y = 0; y < dims[0]; ++y) { for (auto x = 0; x < dims[1]; ++x) { - *p++ = (double)x; - *p++ = (double)y; + // 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; } } @@ -163,7 +167,7 @@ image_resample(py::array input_array, if (is_affine) { convert_trans_affine(transform, params.affine); - params.is_affine = true; + params.is_affine = is_affine; } else { transform_mesh = _get_transform_mesh(transform, output_array.shape()); params.transform_mesh = transform_mesh.data(); @@ -200,6 +204,80 @@ image_resample(py::array input_array, } +// This is used by matplotlib.testing.compare to calculate RMS and a difference image. +static py::tuple +calculate_rms_and_diff(py::array_t expected_image, + py::array_t actual_image) +{ + for (const auto & [image, name] : {std::pair{expected_image, "Expected"}, + std::pair{actual_image, "Actual"}}) + { + if (image.ndim() != 3) { + auto exceptions = py::module_::import("matplotlib.testing.exceptions"); + auto ImageComparisonFailure = exceptions.attr("ImageComparisonFailure"); + py::set_error( + ImageComparisonFailure, + "{name} image must be 3-dimensional, but is {ndim}-dimensional"_s.format( + "name"_a=name, "ndim"_a=image.ndim())); + throw py::error_already_set(); + } + } + + auto height = expected_image.shape(0); + auto width = expected_image.shape(1); + auto depth = expected_image.shape(2); + + if (depth != 3 && depth != 4) { + auto exceptions = py::module_::import("matplotlib.testing.exceptions"); + auto ImageComparisonFailure = exceptions.attr("ImageComparisonFailure"); + py::set_error( + ImageComparisonFailure, + "Image must be RGB or RGBA but has depth {depth}"_s.format( + "depth"_a=depth)); + throw py::error_already_set(); + } + + if (height != actual_image.shape(0) || width != actual_image.shape(1) || + depth != actual_image.shape(2)) { + auto exceptions = py::module_::import("matplotlib.testing.exceptions"); + auto ImageComparisonFailure = exceptions.attr("ImageComparisonFailure"); + py::set_error( + ImageComparisonFailure, + "Image sizes do not match expected size: {expected_image.shape} "_s + "actual size {actual_image.shape}"_s.format( + "expected_image"_a=expected_image, "actual_image"_a=actual_image)); + throw py::error_already_set(); + } + auto expected = expected_image.unchecked<3>(); + auto actual = actual_image.unchecked<3>(); + + py::ssize_t diff_dims[3] = {height, width, 3}; + py::array_t diff_image(diff_dims); + auto diff = diff_image.mutable_unchecked<3>(); + + double total = 0.0; + for (auto i = 0; i < height; i++) { + for (auto j = 0; j < width; j++) { + for (auto k = 0; k < depth; k++) { + auto pixel_diff = static_cast(expected(i, j, k)) - + static_cast(actual(i, j, k)); + + total += pixel_diff*pixel_diff; + + if (k != 3) { // Hard-code a fully solid alpha channel by omitting it. + diff(i, j, k) = static_cast(std::clamp( + abs(pixel_diff) * 10, // Expand differences in luminance domain. + 0.0, 255.0)); + } + } + } + } + total = total / (width * height * depth); + + return py::make_tuple(sqrt(total), diff_image); +} + + PYBIND11_MODULE(_image, m, py::mod_gil_not_used()) { py::enum_(m, "_InterpolationType") @@ -232,4 +310,7 @@ PYBIND11_MODULE(_image, m, py::mod_gil_not_used()) "norm"_a = false, "radius"_a = 1, image_resample__doc__); + + m.def("calculate_rms_and_diff", &calculate_rms_and_diff, + "expected_image"_a, "actual_image"_a); } diff --git a/src/_macosx.m b/src/_macosx.m index 09838eccaf98..9ca6c0749322 100755 --- a/src/_macosx.m +++ b/src/_macosx.m @@ -40,60 +40,84 @@ static bool keyChangeCapsLock = false; /* Keep track of the current mouse up/down state for open/closed cursor hand */ static bool leftMouseGrabbing = false; -/* Keep track of whether stdin has been received */ -static bool stdin_received = false; -static bool stdin_sigint = false; // Global variable to store the original SIGINT handler static PyOS_sighandler_t originalSigintAction = NULL; -// Signal handler for SIGINT, only sets a flag to exit the run loop +// 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) { - stdin_sigint = true; + 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 wait_for_stdin() { - @autoreleasepool { - stdin_received = false; - stdin_sigint = false; + // 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; + } + @autoreleasepool { // Set up a SIGINT handler to interrupt the event loop if ctrl+c comes in too originalSigintAction = PyOS_setsig(SIGINT, handleSigint); // Create an NSFileHandle for standard input NSFileHandle *stdinHandle = [NSFileHandle fileHandleWithStandardInput]; + // Register for data available notifications on standard input - [[NSNotificationCenter defaultCenter] addObserverForName: NSFileHandleDataAvailableNotification - object: stdinHandle - queue: [NSOperationQueue mainQueue] // Use the main queue - usingBlock: ^(NSNotification *notification) { - // Mark that input has been received - stdin_received = true; - } + id notificationID = [[NSNotificationCenter defaultCenter] addObserverForName: NSFileHandleDataAvailableNotification + object: stdinHandle + queue: [NSOperationQueue mainQueue] // Use the main queue + usingBlock: ^(NSNotification *notification) {stopWithEvent();} ]; // Wait in the background for anything that happens to stdin [stdinHandle waitForDataInBackgroundAndNotify]; - // continuously run an event loop until the stdin_received flag is set to exit - while (!stdin_received && !stdin_sigint) { - // This loop is similar to the main event loop and flush_events which have - // Py_[BEGIN|END]_ALLOW_THREADS surrounding the loop. - // This should not be necessary here because PyOS_InputHook releases the GIL for us. - while (true) { - NSEvent *event = [NSApp nextEventMatchingMask: NSEventMaskAny - untilDate: [NSDate distantPast] - inMode: NSDefaultRunLoopMode - dequeue: YES]; - if (!event) { break; } - [NSApp sendEvent: event]; - } - } + // Run the application's event loop, which will be interrupted on stdin or SIGINT + [NSApp run]; + // Remove the input handler as an observer - [[NSNotificationCenter defaultCenter] removeObserver: stdinHandle]; + [[NSNotificationCenter defaultCenter] removeObserver: notificationID]; + // Restore the original SIGINT handler upon exiting the function PyOS_setsig(SIGINT, originalSigintAction); + return 1; } } @@ -234,20 +258,9 @@ static void lazy_init(void) { } static PyObject* -stop(PyObject* self) +stop(PyObject* self, PyObject* _ /* ignored */) { - [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]; + stopWithEvent(); Py_RETURN_NONE; } @@ -257,20 +270,46 @@ static CGFloat _get_device_scale(CGContextRef cr) return pixelSize.width; } -bool -mpl_check_modifier( - NSUInteger modifiers, NSEventModifierFlags flag, - PyObject* list, char const* name) +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() { - bool failed = false; - if (modifiers & flag) { - PyObject* py_name = NULL; - if (!(py_name = PyUnicode_FromString(name)) - || PyList_Append(list, py_name)) { - failed = true; - } - Py_XDECREF(py_name); + 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; } @@ -278,17 +317,14 @@ static CGFloat _get_device_scale(CGContextRef cr) { PyGILState_STATE gstate = PyGILState_Ensure(); PyObject* list = NULL; - if (!(list = PyList_New(0))) { - goto exit; - } NSUInteger modifiers = [event modifierFlags]; - if (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")) { + 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. } -exit: PyGILState_Release(gstate); return list; } @@ -382,20 +418,9 @@ static CGFloat _get_device_scale(CGContextRef cr) // 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. - NSEvent *event; - Py_BEGIN_ALLOW_THREADS - while (true) { - event = [NSApp nextEventMatchingMask: NSEventMaskAny - untilDate: [NSDate distantPast] - inMode: NSDefaultRunLoopMode - dequeue: YES]; - if (!event) { - break; - } - [NSApp sendEvent:event]; - } + flushEvents(); Py_END_ALLOW_THREADS @@ -547,6 +572,8 @@ static CGFloat _get_device_scale(CGContextRef cr) }, }; +static PyTypeObject FigureManagerType; // forward declaration, needed in destroy() + typedef struct { PyObject_HEAD Window* window; @@ -555,6 +582,16 @@ static CGFloat _get_device_scale(CGContextRef cr) static PyObject* FigureManager_new(PyTypeObject *type, PyObject *args, PyObject *kwds) { + if (![NSThread isMainThread]) { + PyErr_SetString( + PyExc_RuntimeError, + "Cannot create a GUI FigureManager outside the main thread " + "using the MacOS backend. Use a non-interactive " + "backend like 'agg' to make plots on worker threads." + ); + return NULL; + } + lazy_init(); Window* window = [Window alloc]; if (!window) { return NULL; } @@ -661,6 +698,25 @@ static CGFloat _get_device_scale(CGContextRef cr) { [self->window close]; self->window = NULL; + + // call super(self, FigureManager).destroy() - it seems we need the + // explicit arguments, and just super() doesn't work in the C API. + PyObject *super_obj = PyObject_CallFunctionObjArgs( + (PyObject *)&PySuper_Type, + (PyObject *)&FigureManagerType, + self, + NULL + ); + if (super_obj == NULL) { + return NULL; // error + } + PyObject *result = PyObject_CallMethod(super_obj, "destroy", NULL); + Py_DECREF(super_obj); + if (result == NULL) { + return NULL; // error + } + Py_DECREF(result); + Py_RETURN_NONE; } @@ -978,7 +1034,7 @@ -(void)save_figure:(id)sender { gil_call_method(toolbar, "save_figure"); } // 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]; + NSTextView* messagebox = [[NSTextView alloc] initWithFrame: rect]; messagebox.textContainer.maximumNumberOfLines = 2; messagebox.textContainer.lineBreakMode = NSLineBreakByTruncatingTail; messagebox.alignment = NSTextAlignmentRight; @@ -988,7 +1044,6 @@ -(void)save_figure:(id)sender { gil_call_method(toolbar, "save_figure"); } /* if selectable, the messagebox can become first responder, * which is not supposed to happen */ [[window contentView] addSubview: messagebox]; - [messagebox release]; [[window contentView] display]; self->messagebox = messagebox; @@ -999,6 +1054,7 @@ -(void)save_figure:(id)sender { gil_call_method(toolbar, "save_figure"); } NavigationToolbar2_dealloc(NavigationToolbar2 *self) { [self->handler release]; + [self->messagebox release]; Py_TYPE(self)->tp_free((PyObject*)self); } @@ -1448,9 +1504,9 @@ - (void)mouseMoved:(NSEvent *)event x = location.x * device_scale; y = location.y * device_scale; process_event( - "MouseEvent", "{s:s, s:O, s:i, s:i, s:N}", + "MouseEvent", "{s:s, s:O, s:i, s:i, s:N, s:N}", "name", "motion_notify_event", "canvas", canvas, "x", x, "y", y, - "modifiers", mpl_modifiers(event)); + "buttons", mpl_buttons(), "modifiers", mpl_modifiers(event)); } - (void)mouseDragged:(NSEvent *)event @@ -1461,9 +1517,9 @@ - (void)mouseDragged:(NSEvent *)event x = location.x * device_scale; y = location.y * device_scale; process_event( - "MouseEvent", "{s:s, s:O, s:i, s:i, s:N}", + "MouseEvent", "{s:s, s:O, s:i, s:i, s:N, s:N}", "name", "motion_notify_event", "canvas", canvas, "x", x, "y", y, - "modifiers", mpl_modifiers(event)); + "buttons", mpl_buttons(), "modifiers", mpl_modifiers(event)); } - (void)rightMouseDown:(NSEvent *)event { [self mouseDown: event]; } @@ -1838,7 +1894,7 @@ - (void)flagsChanged:(NSEvent *)event "written on the file descriptor given as argument.")}, {"stop", (PyCFunction)stop, - METH_NOARGS, + METH_VARARGS, PyDoc_STR("Stop the NSApp.")}, {"show", (PyCFunction)show, @@ -1870,6 +1926,9 @@ - (void)flagsChanged:(NSEvent *)event Py_XDECREF(m); return NULL; } +#ifdef Py_GIL_DISABLED + PyUnstable_Module_SetGIL(m, Py_MOD_GIL_NOT_USED); +#endif return m; } diff --git a/src/_path.h b/src/_path.h index f5c06e4a6a15..226d60231682 100644 --- a/src/_path.h +++ b/src/_path.h @@ -3,12 +3,12 @@ #ifndef MPL_PATH_H #define MPL_PATH_H -#include -#include -#include -#include #include +#include +#include +#include #include +#include #include "agg_conv_contour.h" #include "agg_conv_curve.h" @@ -26,6 +26,8 @@ struct XY double x; double y; + XY() : x(0), y(0) {} + XY(double x_, double y_) : x(x_), y(y_) { } @@ -43,7 +45,8 @@ struct XY typedef std::vector Polygon; -void _finalize_polygon(std::vector &result, int closed_only) +inline void +_finalize_polygon(std::vector &result, bool closed_only) { if (result.size() == 0) { return; @@ -311,43 +314,39 @@ inline bool point_on_path( struct extent_limits { - double x0; - double y0; - double x1; - double y1; - double xm; - double ym; -}; + XY start; + XY end; + /* minpos is the minimum positive values in the data; used by log scaling. */ + XY minpos; -void reset_limits(extent_limits &e) -{ - e.x0 = std::numeric_limits::infinity(); - e.y0 = std::numeric_limits::infinity(); - e.x1 = -std::numeric_limits::infinity(); - e.y1 = -std::numeric_limits::infinity(); - /* xm and ym are the minimum positive values in the data, used - by log scaling */ - e.xm = std::numeric_limits::infinity(); - e.ym = std::numeric_limits::infinity(); -} + extent_limits() : start{0,0}, end{0,0}, minpos{0,0} { + reset(); + } -inline void update_limits(double x, double y, extent_limits &e) -{ - if (x < e.x0) - e.x0 = x; - if (y < e.y0) - e.y0 = y; - if (x > e.x1) - e.x1 = x; - if (y > e.y1) - e.y1 = y; - /* xm and ym are the minimum positive values in the data, used - by log scaling */ - if (x > 0.0 && x < e.xm) - e.xm = x; - if (y > 0.0 && y < e.ym) - e.ym = y; -} + void reset() + { + start.x = std::numeric_limits::infinity(); + start.y = std::numeric_limits::infinity(); + end.x = -std::numeric_limits::infinity(); + end.y = -std::numeric_limits::infinity(); + minpos.x = std::numeric_limits::infinity(); + minpos.y = std::numeric_limits::infinity(); + } + + void update(double x, double y) + { + start.x = std::min(start.x, x); + start.y = std::min(start.y, y); + end.x = std::max(end.x, x); + end.y = std::max(end.y, y); + if (x > 0.0) { + minpos.x = std::min(minpos.x, x); + } + if (y > 0.0) { + minpos.y = std::min(minpos.y, y); + } + } +}; template void update_path_extents(PathIterator &path, agg::trans_affine &trans, extent_limits &extents) @@ -366,7 +365,7 @@ void update_path_extents(PathIterator &path, agg::trans_affine &trans, extent_li if ((code & agg::path_cmd_end_poly) == agg::path_cmd_end_poly) { continue; } - update_limits(x, y, extents); + extents.update(x, y); } } @@ -389,7 +388,7 @@ void get_path_collection_extents(agg::trans_affine &master_transform, agg::trans_affine trans; - reset_limits(extent); + extent.reset(); for (auto i = 0; i < N; ++i) { typename PathGenerator::path_iterator path(paths(i % Npaths)); @@ -524,12 +523,14 @@ struct bisectx { } - inline void bisect(double sx, double sy, double px, double py, double *bx, double *by) const + inline XY bisect(const XY s, const XY p) const { - *bx = m_x; - double dx = px - sx; - double dy = py - sy; - *by = sy + dy * ((m_x - sx) / dx); + double dx = p.x - s.x; + double dy = p.y - s.y; + return { + m_x, + s.y + dy * ((m_x - s.x) / dx), + }; } }; @@ -539,9 +540,9 @@ struct xlt : public bisectx { } - inline bool is_inside(double x, double y) const + inline bool is_inside(const XY point) const { - return x <= m_x; + return point.x <= m_x; } }; @@ -551,9 +552,9 @@ struct xgt : public bisectx { } - inline bool is_inside(double x, double y) const + inline bool is_inside(const XY point) const { - return x >= m_x; + return point.x >= m_x; } }; @@ -565,12 +566,14 @@ struct bisecty { } - inline void bisect(double sx, double sy, double px, double py, double *bx, double *by) const + inline XY bisect(const XY s, const XY p) const { - *by = m_y; - double dx = px - sx; - double dy = py - sy; - *bx = sx + dx * ((m_y - sy) / dy); + double dx = p.x - s.x; + double dy = p.y - s.y; + return { + s.x + dx * ((m_y - s.y) / dy), + m_y, + }; } }; @@ -580,9 +583,9 @@ struct ylt : public bisecty { } - inline bool is_inside(double x, double y) const + inline bool is_inside(const XY point) const { - return y <= m_y; + return point.y <= m_y; } }; @@ -592,9 +595,9 @@ struct ygt : public bisecty { } - inline bool is_inside(double x, double y) const + inline bool is_inside(const XY point) const { - return y >= m_y; + return point.y >= m_y; } }; } @@ -602,7 +605,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(); @@ -610,49 +612,30 @@ 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; - - sinside = filter.is_inside(sx, sy); - pinside = filter.is_inside(px, py); + auto s = polygon.back(); + for (auto p : polygon) { + sinside = filter.is_inside(s); + pinside = filter.is_inside(p); if (sinside ^ pinside) { - filter.bisect(sx, sy, px, py, &bx, &by); - result.push_back(XY(bx, by)); + result.emplace_back(filter.bisect(s, p)); } if (pinside) { - result.push_back(XY(px, py)); + result.emplace_back(p); } - sx = px; - sy = py; + s = p; } } template -void -clip_path_to_rect(PathIterator &path, agg::rect_d &rect, bool inside, std::vector &results) +auto +clip_path_to_rect(PathIterator &path, agg::rect_d &rect, bool inside) { - double xmin, ymin, xmax, ymax; - if (rect.x1 < rect.x2) { - xmin = rect.x1; - xmax = rect.x2; - } else { - xmin = rect.x2; - xmax = rect.x1; - } - - if (rect.y1 < rect.y2) { - ymin = rect.y1; - ymax = rect.y2; - } else { - ymin = rect.y2; - ymax = rect.y1; - } + rect.normalize(); + auto xmin = rect.x1, xmax = rect.x2; + auto ymin = rect.y1, ymax = rect.y2; if (!inside) { std::swap(xmin, xmax); @@ -663,26 +646,27 @@ clip_path_to_rect(PathIterator &path, agg::rect_d &rect, bool inside, std::vecto curve_t curve(path); Polygon polygon1, polygon2; - double x = 0, y = 0; + XY point; unsigned code = 0; curve.rewind(0); + std::vector results; do { // Grab the next subpath and store it in polygon1 polygon1.clear(); do { if (code == agg::path_cmd_move_to) { - polygon1.push_back(XY(x, y)); + polygon1.emplace_back(point); } - code = curve.vertex(&x, &y); + code = curve.vertex(&point.x, &point.y); if (code == agg::path_cmd_stop) { break; } if (code != agg::path_cmd_move_to) { - polygon1.push_back(XY(x, y)); + polygon1.emplace_back(point); } } while ((code & agg::path_cmd_end_poly) != agg::path_cmd_end_poly); @@ -695,12 +679,14 @@ clip_path_to_rect(PathIterator &path, agg::rect_d &rect, bool inside, std::vecto // Empty polygons aren't very useful, so skip them if (polygon1.size()) { - _finalize_polygon(results, 1); + _finalize_polygon(results, true); results.push_back(polygon1); } } while (code != agg::path_cmd_stop); - _finalize_polygon(results, 1); + _finalize_polygon(results, true); + + return results; } template @@ -960,7 +946,7 @@ void convert_path_to_polygons(PathIterator &path, agg::trans_affine &trans, double width, double height, - int closed_only, + bool closed_only, std::vector &result) { typedef agg::conv_transform transformed_path_t; @@ -978,23 +964,20 @@ void convert_path_to_polygons(PathIterator &path, 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(); + _finalize_polygon(result, true); + 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); } } @@ -1058,15 +1041,14 @@ void cleanup_path(PathIterator &path, void quad2cubic(double x0, double y0, double x1, double y1, double x2, double y2, - double *outx, double *outy) + std::array &outx, std::array &outy) { - - outx[0] = x0 + 2./3. * (x1 - x0); - outy[0] = y0 + 2./3. * (y1 - y0); - outx[1] = outx[0] + 1./3. * (x2 - x0); - outy[1] = outy[0] + 1./3. * (y2 - y0); - outx[2] = x2; - outy[2] = y2; + std::get<0>(outx) = x0 + 2./3. * (x1 - x0); + std::get<0>(outy) = y0 + 2./3. * (y1 - y0); + std::get<1>(outx) = std::get<0>(outx) + 1./3. * (x2 - x0); + std::get<1>(outy) = std::get<0>(outy) + 1./3. * (y2 - y0); + std::get<2>(outx) = x2; + std::get<2>(outy) = y2; } @@ -1086,7 +1068,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 @@ -1111,27 +1093,27 @@ void __add_number(double val, char format_code, int precision, template bool __convert_to_string(PathIterator &path, int precision, - char **codes, + const std::array &codes, bool postfix, std::string& buffer) { const char format_code = 'f'; - double x[3]; - double y[3]; + std::array x; + std::array y; double last_x = 0.0; double last_y = 0.0; unsigned code; - while ((code = path.vertex(&x[0], &y[0])) != agg::path_cmd_stop) { + while ((code = path.vertex(&std::get<0>(x), &std::get<0>(y))) != agg::path_cmd_stop) { if (code == CLOSEPOLY) { - buffer += codes[4]; + buffer += std::get<4>(codes); } else if (code < 5) { size_t size = NUM_VERTICES[code]; for (size_t i = 1; i < size; ++i) { - unsigned subcode = path.vertex(&x[i], &y[i]); + unsigned subcode = path.vertex(&x.at(i), &y.at(i)); if (subcode != code) { return false; } @@ -1140,29 +1122,29 @@ bool __convert_to_string(PathIterator &path, /* For formats that don't support quad curves, convert to cubic curves */ if (code == CURVE3 && codes[code - 1][0] == '\0') { - quad2cubic(last_x, last_y, x[0], y[0], x[1], y[1], x, y); + quad2cubic(last_x, last_y, x.at(0), y.at(0), x.at(1), y.at(1), x, y); code++; size = 3; } if (!postfix) { - buffer += codes[code - 1]; + buffer += codes.at(code - 1); buffer += ' '; } for (size_t i = 0; i < size; ++i) { - __add_number(x[i], format_code, precision, buffer); + __add_number(x.at(i), format_code, precision, buffer); buffer += ' '; - __add_number(y[i], format_code, precision, buffer); + __add_number(y.at(i), format_code, precision, buffer); buffer += ' '; } if (postfix) { - buffer += codes[code - 1]; + buffer += codes.at(code - 1); } - last_x = x[size - 1]; - last_y = y[size - 1]; + last_x = x.at(size - 1); + last_y = y.at(size - 1); } else { // Unknown code value return false; @@ -1181,7 +1163,7 @@ bool convert_to_string(PathIterator &path, bool simplify, SketchParams sketch_params, int precision, - char **codes, + const std::array &codes, bool postfix, std::string& buffer) { @@ -1218,7 +1200,6 @@ bool convert_to_string(PathIterator &path, sketch_t sketch(curve, sketch_params.scale, sketch_params.length, sketch_params.randomness); return __convert_to_string(sketch, precision, codes, postfix, buffer); } - } template diff --git a/src/_path_wrapper.cpp b/src/_path_wrapper.cpp index e8322cb51b7b..802189c428d3 100644 --- a/src/_path_wrapper.cpp +++ b/src/_path_wrapper.cpp @@ -52,64 +52,6 @@ Py_points_in_path(py::array_t points_obj, double r, mpl::PathIterator pa return results; } -static py::tuple -Py_update_path_extents(mpl::PathIterator path, agg::trans_affine trans, - agg::rect_d rect, py::array_t minpos, bool ignore) -{ - bool changed; - - if (minpos.ndim() != 1) { - throw py::value_error( - "minpos must be 1D, got " + std::to_string(minpos.ndim())); - } - if (minpos.shape(0) != 2) { - throw py::value_error( - "minpos must be of length 2, got " + std::to_string(minpos.shape(0))); - } - - extent_limits e; - - if (ignore) { - 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.data(0); - e.ym = *minpos.data(1); - } - - 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.data(0) || e.ym != *minpos.data(1)); - - py::ssize_t extentsdims[] = { 2, 2 }; - py::array_t outextents(extentsdims); - *outextents.mutable_data(0, 0) = e.x0; - *outextents.mutable_data(0, 1) = e.y0; - *outextents.mutable_data(1, 0) = e.x1; - *outextents.mutable_data(1, 1) = e.y1; - - py::ssize_t minposdims[] = { 2 }; - py::array_t outminpos(minposdims); - *outminpos.mutable_data(0) = e.xm; - *outminpos.mutable_data(1) = e.ym; - - return py::make_tuple(outextents, outminpos, changed); -} - static py::tuple Py_get_path_collection_extents(agg::trans_affine master_transform, mpl::PathGenerator paths, @@ -126,15 +68,15 @@ Py_get_path_collection_extents(agg::trans_affine master_transform, 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; + *extents.mutable_data(0, 0) = e.start.x; + *extents.mutable_data(0, 1) = e.start.y; + *extents.mutable_data(1, 0) = e.end.x; + *extents.mutable_data(1, 1) = e.end.y; py::ssize_t minposdims[] = { 2 }; py::array_t minpos(minposdims); - *minpos.mutable_data(0) = e.xm; - *minpos.mutable_data(1) = e.ym; + *minpos.mutable_data(0) = e.minpos.x; + *minpos.mutable_data(1) = e.minpos.y; return py::make_tuple(extents, minpos); } @@ -167,9 +109,7 @@ Py_path_in_path(mpl::PathIterator a, agg::trans_affine atrans, static py::list Py_clip_path_to_rect(mpl::PathIterator path, agg::rect_d rect, bool inside) { - std::vector result; - - clip_path_to_rect(path, rect, inside, result); + auto result = clip_path_to_rect(path, rect, inside); return convert_polygon_vector(result); } @@ -310,16 +250,11 @@ 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) + const std::array &codes, bool postfix) { - char *codes[5]; std::string buffer; bool status; - for (auto i = 0; i < 5; ++i) { - codes[i] = const_cast(codes_obj[i].c_str()); - } - if (!simplify.has_value()) { simplify = path.should_simplify(); } @@ -374,8 +309,6 @@ PYBIND11_MODULE(_path, m, py::mod_gil_not_used()) "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("update_path_extents", &Py_update_path_extents, - "path"_a, "trans"_a, "rect"_a, "minpos"_a, "ignore"_a); m.def("get_path_collection_extents", &Py_get_path_collection_extents, "master_transform"_a, "paths"_a, "transforms"_a, "offsets"_a, "offset_transform"_a); diff --git a/src/_qhull_wrapper.cpp b/src/_qhull_wrapper.cpp index da623a8d1b71..f8a3103b65f1 100644 --- a/src/_qhull_wrapper.cpp +++ b/src/_qhull_wrapper.cpp @@ -167,13 +167,13 @@ delaunay_impl(py::ssize_t npoints, const double* x, const double* y, } /* qhull expects a FILE* to write errors to. */ - FILE* error_file = NULL; + 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 == NULL) { + if (error_file == nullptr) { throw std::runtime_error("Could not open devnull"); } } @@ -186,7 +186,7 @@ delaunay_impl(py::ssize_t npoints, const double* x, const double* y, 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", NULL, error_file); + (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={})") diff --git a/src/_tkagg.cpp b/src/_tkagg.cpp index 04ca3d4d1b84..955ce2103f90 100644 --- a/src/_tkagg.cpp +++ b/src/_tkagg.cpp @@ -7,7 +7,7 @@ // 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 #include #include @@ -92,6 +92,7 @@ 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 void mpl_tk_blit(py::object interp_obj, const char *photo_name, @@ -173,7 +174,15 @@ DpiSubclassProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, std::string dpi = std::to_string(LOWORD(wParam)); Tcl_Interp* interp = (Tcl_Interp*)dwRefData; - TCL_SETVAR(interp, var_name.c_str(), dpi.c_str(), 0); + 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: @@ -246,13 +255,16 @@ bool load_tcl_tk(T lib) 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 && TK_FIND_PHOTO && TK_PHOTO_PUT_BLOCK; + return (TCL_SETVAR || TCL_SETVAR2) && TK_FIND_PHOTO && TK_PHOTO_PUT_BLOCK; } #ifdef WIN32_DLL @@ -300,7 +312,7 @@ load_tkinter_funcs() // Load tkinter global funcs from tkinter compiled module. // Try loading from the main program namespace first. - auto main_program = dlopen(NULL, RTLD_LAZY); + 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)) { @@ -343,8 +355,8 @@ PYBIND11_MODULE(_tkagg, m, py::mod_gil_not_used()) throw py::error_already_set(); } - if (!TCL_SETVAR) { - throw py::import_error("Failed to load Tcl_SetVar"); + if (!(TCL_SETVAR || TCL_SETVAR2)) { + throw py::import_error("Failed to load Tcl_SetVar or Tcl_SetVar2"); } else if (!TK_FIND_PHOTO) { throw py::import_error("Failed to load Tk_FindPhoto"); } else if (!TK_PHOTO_PUT_BLOCK) { diff --git a/src/_tkmini.h b/src/_tkmini.h index 85f245815e4c..1c74cf9720f8 100644 --- a/src/_tkmini.h +++ b/src/_tkmini.h @@ -104,6 +104,9 @@ typedef int (*Tk_PhotoPutBlock_t) (Tcl_Interp *interp, Tk_PhotoHandle handle, /* 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/agg_workaround.h b/src/agg_workaround.h index 476219519280..f1cba6f570d8 100644 --- a/src/agg_workaround.h +++ b/src/agg_workaround.h @@ -2,52 +2,13 @@ #define MPL_AGG_WORKAROUND_H #include "agg_pixfmt_rgba.h" +#include "agg_trans_affine.h" /********************************************************************** WORKAROUND: This class is to workaround a bug in Agg SVN where the 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 { @@ -82,4 +43,96 @@ struct fixed_blender_rgba_plain : agg::conv_rgba_plain } }; + +/********************************************************************** + This class provides higher-accuracy nearest-neighbor interpolation for + affine transforms than span_interpolator_linear by using + floating-point-based interpolation instead of integer-based +*/ + +template +class accurate_interpolator_affine_nn +{ +public: + typedef Transformer trans_type; + + enum subpixel_scale_e + { + subpixel_shift = SubpixelShift, + subpixel_scale = 1 << subpixel_shift + }; + + //-------------------------------------------------------------------- + accurate_interpolator_affine_nn() {} + accurate_interpolator_affine_nn(trans_type& trans) : m_trans(&trans) {} + accurate_interpolator_affine_nn(trans_type& trans, + double x, double y, unsigned len) : + m_trans(&trans) + { + begin(x, y, len); + } + + //---------------------------------------------------------------- + const trans_type& transformer() const { return *m_trans; } + void transformer(trans_type& trans) { m_trans = &trans; } + + //---------------------------------------------------------------- + void begin(double x, double y, unsigned len) + { + m_len = len - 1; + m_cur = 0; + + m_stx1 = x; + m_sty1 = y; + m_trans->transform(&m_stx1, &m_sty1); + m_stx1 *= subpixel_scale; + m_sty1 *= subpixel_scale; + + m_stx2 = x + m_len; + m_sty2 = y; + m_trans->transform(&m_stx2, &m_sty2); + m_stx2 *= subpixel_scale; + m_sty2 *= subpixel_scale; + } + + //---------------------------------------------------------------- + void resynchronize(double xe, double ye, unsigned len) + { + m_len = len - 1; + m_cur = 0; + + m_trans->transform(&xe, &ye); + m_stx2 = xe * subpixel_scale; + m_sty2 = ye * subpixel_scale; + } + + //---------------------------------------------------------------- + void operator++() + { + m_cur++; + } + + //---------------------------------------------------------------- + void coordinates(int* x, int* y) const + { + // Truncate instead of round because this interpolator needs to + // match the definitions for nearest-neighbor interpolation + if (m_cur == m_len) + { + *x = int(m_stx2); + *y = int(m_sty2); + } + else + { + // Add a tiny fudge amount to account for numerical precision loss + *x = int(m_stx1 + (m_stx2 - m_stx1) * m_cur / m_len + 1e-8); + *y = int(m_sty1 + (m_sty2 - m_sty1) * m_cur / m_len + 1e-8); + } + } + +private: + trans_type* m_trans; + unsigned m_len, m_cur; + double m_stx1, m_sty1, m_stx2, m_sty2; +}; #endif diff --git a/src/array.h b/src/array.h index 97d66dd4a6d2..0e8db3c4cac7 100644 --- a/src/array.h +++ b/src/array.h @@ -56,9 +56,7 @@ class empty public: typedef empty sub_t; - empty() - { - } + empty() = default; T &operator()(int i, int j = 0, int k = 0) { diff --git a/src/ft2font.cpp b/src/ft2font.cpp index c0e8b7c27125..da1bd19dca57 100644 --- a/src/ft2font.cpp +++ b/src/ft2font.cpp @@ -1,5 +1,8 @@ /* -*- mode: c++; c-basic-offset: 4 -*- */ +#include "ft2font.h" +#include "mplutils.h" + #include #include #include @@ -9,9 +12,6 @@ #include #include -#include "ft2font.h" -#include "mplutils.h" - #ifndef M_PI #define M_PI 3.14159265358979323846264338328 #endif @@ -43,71 +43,23 @@ FT_Library _ft2Library; -// FreeType error codes; loaded as per fterror.h. -static 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 -} - -void throw_ft_error(std::string message, FT_Error error) { - char const* s = ft_error_string(error); - std::ostringstream os(""); - if (s) { - os << message << " (" << s << "; error code 0x" << std::hex << error << ")"; - } else { // Should not occur, but don't add another error from failed lookup. - os << message << " (error code 0x" << std::hex << error << ")"; - } - throw std::runtime_error(os.str()); -} - -FT2Image::FT2Image() : m_buffer(NULL), m_width(0), m_height(0) -{ -} - FT2Image::FT2Image(unsigned long width, unsigned long height) - : 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; - - 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; - } + auto buf = im.mutable_data(0); - if (numBytes && m_buffer) { - memset(m_buffer, 0, numBytes); - } -} - -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; @@ -121,14 +73,14 @@ void FT2Image::draw_bitmap(FT_Bitmap *bitmap, FT_Int x, FT_Int 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); @@ -258,43 +210,31 @@ FT2Font::get_path(std::vector &vertices, std::vector &cod FT2Font::FT2Font(FT_Open_Args &open_args, long hinting_factor_, std::vector &fallback_list, - FT2Font::WarnFunc warn) - : ft_glyph_warn(warn), image(), face(NULL) + 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) { - 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); + throw; } - - if (open_args.stream != NULL) { - face->face_flags |= FT_FACE_FLAG_EXTERNAL_STREAM; - } - - 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) { @@ -308,31 +248,29 @@ void FT2Font::clear() 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 (size_t i = 0; i < fallbacks.size(); i++) { - fallbacks[i]->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 (size_t i = 0; i < fallbacks.size(); i++) { - fallbacks[i]->set_size(ptsize, dpi); + for (auto & fallback : fallbacks) { + fallback->set_size(ptsize, dpi); } } @@ -341,17 +279,12 @@ 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, @@ -393,8 +326,8 @@ int FT2Font::get_kerning(FT_UInt left, FT_UInt right, FT_Kerning_Mode mode, void FT2Font::set_kerning_factor(int factor) { kerning_factor = factor; - for (size_t i = 0; i < fallbacks.size(); i++) { - fallbacks[i]->set_kerning_factor(factor); + for (auto & fallback : fallbacks) { + fallback->set_kerning_factor(factor); } } @@ -420,7 +353,7 @@ void FT2Font::set_text( bbox.xMax = bbox.yMax = -32000; FT_UInt previous = 0; - FT2Font *previous_ft_object = NULL; + FT2Font *previous_ft_object = nullptr; for (auto codepoint : text) { FT_UInt glyph_index = 0; @@ -441,6 +374,8 @@ void FT2Font::set_text( 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 @@ -456,8 +391,8 @@ void FT2Font::set_text( FT_Glyph &thisGlyph = glyphs[glyphs.size() - 1]; last_advance = ft_object_with_glyph->get_face()->glyph->advance.x; - FT_Glyph_Transform(thisGlyph, 0, &pen); - FT_Glyph_Transform(thisGlyph, &matrix, 0); + FT_Glyph_Transform(thisGlyph, nullptr, &pen); + FT_Glyph_Transform(thisGlyph, &matrix, nullptr); xys.push_back(pen.x); xys.push_back(pen.y); @@ -492,7 +427,7 @@ void FT2Font::load_char(long charcode, FT_Int32 flags, FT2Font *&ft_object, bool 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 = NULL; + FT2Font *throwaway = nullptr; ft_object->load_char(charcode, flags, throwaway, false); } else if (fallback) { FT_UInt final_glyph_index; @@ -505,11 +440,13 @@ void FT2Font::load_char(long charcode, FT_Int32 flags, FT2Font *&ft_object, bool if (!was_found) { ft_glyph_warn(charcode, glyph_seen_fonts); if (charcode_error) { - throw_ft_error("Could not load charcode", charcode_error); + THROW_FT_ERROR("charcode loading", charcode_error); } else if (glyph_error) { - throw_ft_error("Could not load charcode", 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 { @@ -517,16 +454,12 @@ void FT2Font::load_char(long charcode, FT_Int32 flags, FT2Font *&ft_object, bool ft_object = this; FT_UInt glyph_index = FT_Get_Char_Index(face, (FT_ULong) charcode); if (!glyph_index){ - glyph_seen_fonts.insert((face != NULL)?face->family_name: NULL); + glyph_seen_fonts.insert((face != nullptr)?face->family_name: nullptr); ft_glyph_warn((FT_ULong)charcode, glyph_seen_fonts); } - if (FT_Error error = FT_Load_Glyph(face, glyph_index, flags)) { - throw_ft_error("Could not load charcode", error); - } + 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); } } @@ -569,7 +502,9 @@ bool FT2Font::load_char_with_fallback(FT2Font *&ft_object_with_glyph, bool override = false) { FT_UInt glyph_index = FT_Get_Char_Index(face, charcode); - glyph_seen_fonts.insert(face->family_name); + if (!warn_if_used) { + glyph_seen_fonts.insert(face->family_name); + } if (glyph_index || override) { charcode_error = FT_Load_Glyph(face, glyph_index, flags); @@ -594,8 +529,8 @@ bool FT2Font::load_char_with_fallback(FT2Font *&ft_object_with_glyph, return true; } else { - for (size_t i = 0; i < fallbacks.size(); ++i) { - bool was_found = fallbacks[i]->load_char_with_fallback( + 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); @@ -624,19 +559,15 @@ void FT2Font::load_glyph(FT_UInt glyph_index, void FT2Font::load_glyph(FT_UInt glyph_index, FT_Int32 flags) { - if (FT_Error error = FT_Load_Glyph(face, glyph_index, flags)) { - throw_ft_error("Could not load glyph", error); - } + 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 = NULL; + 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 @@ -672,27 +603,27 @@ void FT2Font::draw_glyphs_to_bitmap(bool antialiased) long width = (bbox.xMax - bbox.xMin) / 64 + 2; long height = (bbox.yMax - bbox.yMin) / 64 + 2; - image.resize(width, height); - - 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 * (1. / 64.))); FT_Int y = (FT_Int)((bbox.yMax * (1. / 64.)) - bitmap->top + 1); - image.draw_bitmap(&bitmap->bitmap, x, 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); @@ -702,19 +633,15 @@ 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, std::string &buffer, @@ -736,9 +663,7 @@ void FT2Font::get_glyph_name(unsigned int glyph_number, std::string &buffer, throw std::runtime_error("Failed to convert glyph to standard name"); } } else { - if (FT_Error error = FT_Get_Glyph_Name(face, glyph_number, buffer.data(), buffer.size())) { - 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 5524930d5ad0..6676a7dd4818 100644 --- a/src/ft2font.h +++ b/src/ft2font.h @@ -6,6 +6,9 @@ #ifndef MPL_FT2FONT_H #define MPL_FT2FONT_H +#include +#include + #include #include #include @@ -22,17 +25,43 @@ extern "C" { #include FT_TRUETYPE_TABLES_H } -/* - By definition, FT_FIXED as 2 16bit values stored in a single long. - */ +namespace py = pybind11; + +// 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(); @@ -71,7 +100,8 @@ class FT2Font public: FT2Font(FT_Open_Args &open_args, long hinting_factor, - std::vector &fallback_list, WarnFunc warn); + std::vector &fallback_list, + WarnFunc warn, bool warn_if_used); virtual ~FT2Font(); void clear(); void set_size(double ptsize, double dpi); @@ -100,7 +130,9 @@ class FT2Font void get_bitmap_offset(long *x, long *y); long get_descent(); void draw_glyphs_to_bitmap(bool antialiased); - void draw_glyph_to_bitmap(FT2Image &im, int x, int y, size_t glyphInd, bool antialiased); + 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); FT_UInt get_char_index(FT_ULong charcode, bool fallback); @@ -112,7 +144,7 @@ class FT2Font return face; } - FT2Image &get_image() + py::array_t &get_image() { return image; } @@ -139,7 +171,8 @@ class FT2Font private: WarnFunc ft_glyph_warn; - FT2Image image; + bool warn_if_used; + py::array_t image; FT_Face face; FT_Vector pen; /* untransformed origin */ std::vector glyphs; diff --git a/src/ft2font_wrapper.cpp b/src/ft2font_wrapper.cpp index 9f9fab999707..ca2db6aa0e5b 100644 --- a/src/ft2font_wrapper.cpp +++ b/src/ft2font_wrapper.cpp @@ -66,6 +66,19 @@ const char *FaceFlags__doc__ = R"""( .. versionadded:: 3.10 )"""; +#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 + enum class FaceFlags : FT_Long { #define DECLARE_FLAG(name) name = FT_FACE_FLAG_##name DECLARE_FLAG(SCALABLE), @@ -83,18 +96,10 @@ enum class FaceFlags : FT_Long { DECLARE_FLAG(CID_KEYED), DECLARE_FLAG(TRICKY), DECLARE_FLAG(COLOR), -#ifdef FT_FACE_FLAG_VARIATION // backcompat: ft 2.9.0. DECLARE_FLAG(VARIATION), -#endif -#ifdef FT_FACE_FLAG_SVG // backcompat: ft 2.12.0. DECLARE_FLAG(SVG), -#endif -#ifdef FT_FACE_FLAG_SBIX // backcompat: ft 2.12.0. DECLARE_FLAG(SBIX), -#endif -#ifdef FT_FACE_FLAG_SBIX_OVERLAY // backcompat: ft 2.12.0. DECLARE_FLAG(SBIX_OVERLAY), -#endif #undef DECLARE_FLAG }; @@ -115,14 +120,10 @@ P11X_DECLARE_ENUM( {"CID_KEYED", FaceFlags::CID_KEYED}, {"TRICKY", FaceFlags::TRICKY}, {"COLOR", FaceFlags::COLOR}, - // backcompat: ft 2.9.0. - // {"VARIATION", FaceFlags::VARIATION}, - // backcompat: ft 2.12.0. - // {"SVG", FaceFlags::SVG}, - // backcompat: ft 2.12.0. - // {"SBIX", FaceFlags::SBIX}, - // backcompat: ft 2.12.0. - // {"SBIX_OVERLAY", FaceFlags::SBIX_OVERLAY}, + {"VARIATION", FaceFlags::VARIATION}, + {"SVG", FaceFlags::SVG}, + {"SBIX", FaceFlags::SBIX}, + {"SBIX_OVERLAY", FaceFlags::SBIX_OVERLAY}, ); const char *LoadFlags__doc__ = R"""( @@ -134,6 +135,16 @@ const char *LoadFlags__doc__ = R"""( .. 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 + enum class LoadFlags : FT_Int32 { #define DECLARE_FLAG(name) name = FT_LOAD_##name DECLARE_FLAG(DEFAULT), @@ -152,15 +163,9 @@ enum class LoadFlags : FT_Int32 { DECLARE_FLAG(LINEAR_DESIGN), DECLARE_FLAG(NO_AUTOHINT), DECLARE_FLAG(COLOR), -#ifdef FT_LOAD_COMPUTE_METRICS // backcompat: ft 2.6.1. DECLARE_FLAG(COMPUTE_METRICS), -#endif -#ifdef FT_LOAD_BITMAP_METRICS_ONLY // backcompat: ft 2.7.1. - DECLARE_FLAG(LOAD_BITMAP_METRICS_ONLY), -#endif -#ifdef FT_LOAD_NO_SVG // backcompat: ft 2.13.1. - DECLARE_FLAG(LOAD_NO_SVG), -#endif + DECLARE_FLAG(BITMAP_METRICS_ONLY), + DECLARE_FLAG(NO_SVG), DECLARE_FLAG(TARGET_NORMAL), DECLARE_FLAG(TARGET_LIGHT), DECLARE_FLAG(TARGET_MONO), @@ -187,12 +192,9 @@ P11X_DECLARE_ENUM( {"LINEAR_DESIGN", LoadFlags::LINEAR_DESIGN}, {"NO_AUTOHINT", LoadFlags::NO_AUTOHINT}, {"COLOR", LoadFlags::COLOR}, - // backcompat: ft 2.6.1. {"COMPUTE_METRICS", LoadFlags::COMPUTE_METRICS}, - // backcompat: ft 2.7.1. - // {"BITMAP_METRICS_ONLY", LoadFlags::BITMAP_METRICS_ONLY}, - // backcompat: ft 2.13.1. - // {"NO_SVG", LoadFlags::NO_SVG}, + {"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}, @@ -433,6 +435,12 @@ const char *PyFT2Font_init__doc__ = R"""( _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. )"""; @@ -440,16 +448,16 @@ const char *PyFT2Font_init__doc__ = R"""( static PyFT2Font * PyFT2Font_init(py::object filename, long hinting_factor = 8, std::optional> fallback_list = std::nullopt, - int kerning_factor = 0) + int kerning_factor = 0, bool warn_if_used = false) { if (hinting_factor <= 0) { throw py::value_error("hinting_factor must be greater than 0"); } PyFT2Font *self = new PyFT2Font(); - self->x = NULL; + self->x = nullptr; memset(&self->stream, 0, sizeof(FT_StreamRec)); - self->stream.base = NULL; + self->stream.base = nullptr; self->stream.size = 0x7fffffff; // Unknown size. self->stream.pos = 0; self->stream.descriptor.pointer = self; @@ -486,16 +494,27 @@ PyFT2Font_init(py::object filename, long hinting_factor = 8, "First argument must be a path to a font file or a binary-mode file object"); } self->py_file = filename; - self->stream.close = NULL; + self->stream.close = nullptr; } - self->x = new FT2Font(open_args, hinting_factor, fallback_fonts, ft_glyph_warn); + self->x = new FT2Font(open_args, hinting_factor, fallback_fonts, ft_glyph_warn, + warn_if_used); self->x->set_kerning_factor(kerning_factor); return self; } +static py::str +PyFT2Font_fname(PyFT2Font *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 all the glyphs, reset for a new call to `.set_text`."; @@ -762,7 +781,7 @@ PyFT2Font_load_char(PyFT2Font *self, long charcode, std::variant flags_or_int = LoadFlags::FORCE_AUTOHINT) { bool fallback = true; - FT2Font *ft_object = NULL; + FT2Font *ft_object = nullptr; LoadFlags flags; if (auto value = std::get_if(&flags_or_int)) { @@ -816,7 +835,7 @@ PyFT2Font_load_glyph(PyFT2Font *self, FT_UInt glyph_index, std::variant flags_or_int = LoadFlags::FORCE_AUTOHINT) { bool fallback = true; - FT2Font *ft_object = NULL; + FT2Font *ft_object = nullptr; LoadFlags flags; if (auto value = std::get_if(&flags_or_int)) { @@ -949,7 +968,7 @@ const char *PyFT2Font_draw_glyph_to_bitmap__doc__ = R"""( Parameters ---------- - image : FT2Image + 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. @@ -964,14 +983,16 @@ const char *PyFT2Font_draw_glyph_to_bitmap__doc__ = R"""( )"""; static void -PyFT2Font_draw_glyph_to_bitmap(PyFT2Font *self, FT2Image &image, +PyFT2Font_draw_glyph_to_bitmap(PyFT2Font *self, py::buffer &image, double_or_ vxd, double_or_ vyd, PyGlyph *glyph, bool antialiased = true) { auto xd = _double_to_("x", vxd); auto yd = _double_to_("y", vyd); - self->x->draw_glyph_to_bitmap(image, xd, yd, glyph->glyphInd, antialiased); + self->x->draw_glyph_to_bitmap( + py::array_t{image}, + xd, yd, glyph->glyphInd, antialiased); } const char *PyFT2Font_get_glyph_name__doc__ = R"""( @@ -1421,154 +1442,33 @@ const char *PyFT2Font_get_image__doc__ = R"""( static py::array PyFT2Font_get_image(PyFT2Font *self) { - FT2Image &im = self->x->get_image(); - py::ssize_t dims[] = { - static_cast(im.get_height()), - static_cast(im.get_width()) - }; - return py::array_t(dims, im.get_buffer()); -} - -static const char * -PyFT2Font_postscript_name(PyFT2Font *self) -{ - const char *ps_name = FT_Get_Postscript_Name(self->x->get_face()); - if (ps_name == NULL) { - ps_name = "UNAVAILABLE"; - } - - return ps_name; -} - -static FT_Long -PyFT2Font_num_faces(PyFT2Font *self) -{ - return self->x->get_face()->num_faces; -} - -static const char * -PyFT2Font_family_name(PyFT2Font *self) -{ - const char *name = self->x->get_face()->family_name; - if (name == NULL) { - name = "UNAVAILABLE"; - } - return name; -} - -static const char * -PyFT2Font_style_name(PyFT2Font *self) -{ - const char *name = self->x->get_face()->style_name; - if (name == NULL) { - name = "UNAVAILABLE"; - } - return name; -} - -static FaceFlags -PyFT2Font_face_flags(PyFT2Font *self) -{ - return static_cast(self->x->get_face()->face_flags); + return self->x->get_image(); } -static StyleFlags -PyFT2Font_style_flags(PyFT2Font *self) -{ - return static_cast(self->x->get_face()->style_flags); -} +const char *PyFT2Font__get_type1_encoding_vector__doc__ = R"""( + Return a list mapping CharString indices of a Type 1 font to FreeType glyph indices. -static FT_Long -PyFT2Font_num_glyphs(PyFT2Font *self) -{ - return self->x->get_face()->num_glyphs; -} - -static FT_Int -PyFT2Font_num_fixed_sizes(PyFT2Font *self) -{ - return self->x->get_face()->num_fixed_sizes; -} - -static FT_Int -PyFT2Font_num_charmaps(PyFT2Font *self) -{ - return self->x->get_face()->num_charmaps; -} - -static bool -PyFT2Font_scalable(PyFT2Font *self) -{ - if (FT_IS_SCALABLE(self->x->get_face())) { - return true; - } - return false; -} - -static FT_UShort -PyFT2Font_units_per_EM(PyFT2Font *self) -{ - return self->x->get_face()->units_per_EM; -} - -static py::tuple -PyFT2Font_get_bbox(PyFT2Font *self) -{ - FT_BBox *bbox = &(self->x->get_face()->bbox); - - return py::make_tuple(bbox->xMin, bbox->yMin, bbox->xMax, bbox->yMax); -} - -static FT_Short -PyFT2Font_ascender(PyFT2Font *self) -{ - return self->x->get_face()->ascender; -} - -static FT_Short -PyFT2Font_descender(PyFT2Font *self) -{ - return self->x->get_face()->descender; -} - -static FT_Short -PyFT2Font_height(PyFT2Font *self) -{ - return self->x->get_face()->height; -} - -static FT_Short -PyFT2Font_max_advance_width(PyFT2Font *self) -{ - return self->x->get_face()->max_advance_width; -} - -static FT_Short -PyFT2Font_max_advance_height(PyFT2Font *self) -{ - return self->x->get_face()->max_advance_height; -} - -static FT_Short -PyFT2Font_underline_position(PyFT2Font *self) -{ - return self->x->get_face()->underline_position; -} - -static FT_Short -PyFT2Font_underline_thickness(PyFT2Font *self) -{ - return self->x->get_face()->underline_thickness; -} + Returns + ------- + list[int] +)"""; -static py::str -PyFT2Font_fname(PyFT2Font *self) +static std::array +PyFT2Font__get_type1_encoding_vector(PyFT2Font *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); + 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 @@ -1662,6 +1562,10 @@ PYBIND11_MODULE(ft2font, m, py::mod_gil_not_used()) 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) @@ -1701,11 +1605,12 @@ PYBIND11_MODULE(ft2font, m, py::mod_gil_not_used()) .def_property_readonly("bbox", &PyGlyph_get_bbox, "The control box of the glyph."); - py::class_(m, "FT2Font", py::is_final(), py::buffer_protocol(), - PyFT2Font__doc__) + 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, @@ -1735,10 +1640,20 @@ PYBIND11_MODULE(ft2font, m, py::mod_gil_not_used()) .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__) - .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__) + 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__) @@ -1753,55 +1668,110 @@ PYBIND11_MODULE(ft2font, m, py::mod_gil_not_used()) 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_postscript_name, - "PostScript name of the font.") - .def_property_readonly("num_faces", &PyFT2Font_num_faces, - "Number of faces in file.") - .def_property_readonly("family_name", &PyFT2Font_family_name, - "Face family name.") - .def_property_readonly("style_name", &PyFT2Font_style_name, - "Style name.") - .def_property_readonly("face_flags", &PyFT2Font_face_flags, - "Face flags; see `.FaceFlags`.") - .def_property_readonly("style_flags", &PyFT2Font_style_flags, - "Style flags; see `.StyleFlags`.") - .def_property_readonly("num_glyphs", &PyFT2Font_num_glyphs, - "Number of glyphs in the face.") - .def_property_readonly("num_fixed_sizes", &PyFT2Font_num_fixed_sizes, - "Number of bitmap in the face.") - .def_property_readonly("num_charmaps", &PyFT2Font_num_charmaps, - "Number of charmaps in the face.") - .def_property_readonly("scalable", &PyFT2Font_scalable, - "Whether face is scalable; attributes after this one " - "are only defined for scalable faces.") - .def_property_readonly("units_per_EM", &PyFT2Font_units_per_EM, - "Number of font units covered by the EM.") - .def_property_readonly("bbox", &PyFT2Font_get_bbox, - "Face global bounding box (xmin, ymin, xmax, ymax).") - .def_property_readonly("ascender", &PyFT2Font_ascender, - "Ascender in 26.6 units.") - .def_property_readonly("descender", &PyFT2Font_descender, - "Descender in 26.6 units.") - .def_property_readonly("height", &PyFT2Font_height, - "Height in 26.6 units; used to compute a default line " - "spacing (baseline-to-baseline distance).") - .def_property_readonly("max_advance_width", &PyFT2Font_max_advance_width, - "Maximum horizontal cursor advance for all glyphs.") - .def_property_readonly("max_advance_height", &PyFT2Font_max_advance_height, - "Maximum vertical cursor advance for all glyphs.") - .def_property_readonly("underline_position", &PyFT2Font_underline_position, - "Vertical position of the underline bar.") - .def_property_readonly("underline_thickness", &PyFT2Font_underline_thickness, - "Thickness of the underline bar.") - .def_property_readonly("fname", &PyFT2Font_fname, - "The original filename for this object.") + .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 { - FT2Image &im = self.x->get_image(); - std::vector shape { im.get_height(), im.get_width() }; - std::vector strides { im.get_width(), 1 }; - return py::buffer_info(im.get_buffer(), shape, strides); + return self.x->get_image().request(); }); m.attr("__freetype_version__") = version_string; diff --git a/src/meson.build b/src/meson.build index a7018f0db094..d479a8b84aa2 100644 --- a/src/meson.build +++ b/src/meson.build @@ -37,7 +37,7 @@ extension_data = { '_backend_agg.cpp', '_backend_agg_wrapper.cpp', ), - 'dependencies': [agg_dep, freetype_dep, pybind11_dep], + 'dependencies': [agg_dep, pybind11_dep], }, '_c_internal_utils': { 'subdir': 'matplotlib', diff --git a/src/path_converters.h b/src/path_converters.h index a91b7387581f..1482aeed95f8 100644 --- a/src/path_converters.h +++ b/src/path_converters.h @@ -59,9 +59,7 @@ class EmbeddedQueue struct item { - item() - { - } + item() = default; inline void set(const unsigned cmd_, const double x_, const double y_) { diff --git a/src/tri/_tri.cpp b/src/tri/_tri.cpp index 10d958c5ea11..b74fcbec26fc 100644 --- a/src/tri/_tri.cpp +++ b/src/tri/_tri.cpp @@ -44,8 +44,7 @@ std::ostream& operator<<(std::ostream& os, const TriEdge& tri_edge) -XY::XY() -{} +XY::XY() = default; XY::XY(const double& x_, const double& y_) : x(x_), y(y_) @@ -184,8 +183,9 @@ void ContourLine::push_back(const XY& point) void ContourLine::write() const { std::cout << "ContourLine of " << size() << " points:"; - for (const_iterator it = begin(); it != end(); ++it) - std::cout << ' ' << *it; + for (const auto & it : *this) { + std::cout << ' ' << it; + } std::cout << std::endl; } @@ -194,8 +194,9 @@ void ContourLine::write() const void write_contour(const Contour& contour) { std::cout << "Contour of " << contour.size() << " lines." << std::endl; - for (Contour::const_iterator it = contour.begin(); it != contour.end(); ++it) - it->write(); + for (const auto & it : contour) { + it.write(); + } } @@ -264,14 +265,13 @@ void Triangulation::calculate_boundaries() // time, initialise the _tri_edge_to_boundary_map. while (!boundary_edges.empty()) { // Start of new boundary. - BoundaryEdges::iterator it = boundary_edges.begin(); + auto it = boundary_edges.cbegin(); int tri = it->tri; int edge = it->edge; - _boundaries.push_back(Boundary()); - Boundary& boundary = _boundaries.back(); + Boundary& boundary = _boundaries.emplace_back(); while (true) { - boundary.push_back(TriEdge(tri, edge)); + boundary.emplace_back(tri, edge); boundary_edges.erase(it); _tri_edge_to_boundary_map[TriEdge(tri, edge)] = BoundaryEdge(_boundaries.size()-1, boundary.size()-1); @@ -321,9 +321,9 @@ void Triangulation::calculate_edges() auto edges = _edges.mutable_data(); int i = 0; - for (EdgeSet::const_iterator it = edge_set.begin(); it != edge_set.end(); ++it) { - edges[i++] = it->start; - edges[i++] = it->end; + for (const auto & it : edge_set) { + edges[i++] = it.start; + edges[i++] = it.end; } } @@ -351,8 +351,7 @@ void Triangulation::calculate_neighbors() for (edge = 0; edge < 3; ++edge) { int start = get_triangle_point(tri, edge); int end = get_triangle_point(tri, (edge+1)%3); - EdgeToTriEdgeMap::iterator it = - edge_to_tri_edge_map.find(Edge(end,start)); + const auto it = edge_to_tri_edge_map.find(Edge(end, start)); if (it == edge_to_tri_edge_map.end()) { // No neighbor edge exists in the edge_to_tri_edge_map, so // add this edge to it. @@ -464,10 +463,8 @@ void Triangulation::get_boundary_edge(const TriEdge& triEdge, int& edge) const { get_boundaries(); // Ensure _tri_edge_to_boundary_map has been created. - TriEdgeToBoundaryMap::const_iterator it = - _tri_edge_to_boundary_map.find(triEdge); - assert(it != _tri_edge_to_boundary_map.end() && - "TriEdge is not on a boundary"); + const auto it = _tri_edge_to_boundary_map.find(triEdge); + assert(it != _tri_edge_to_boundary_map.end() && "TriEdge is not on a boundary"); boundary = it->second.boundary; edge = it->second.edge; } @@ -587,13 +584,12 @@ void Triangulation::set_mask(const MaskArray& mask) void Triangulation::write_boundaries() const { - const Boundaries& bs = get_boundaries(); - std::cout << "Number of boundaries: " << bs.size() << std::endl; - for (Boundaries::const_iterator it = bs.begin(); it != bs.end(); ++it) { - const Boundary& b = *it; - std::cout << " Boundary of " << b.size() << " points: "; - for (Boundary::const_iterator itb = b.begin(); itb != b.end(); ++itb) { - std::cout << *itb << ", "; + const Boundaries& boundaries = get_boundaries(); + std::cout << "Number of boundaries: " << boundaries.size() << std::endl; + for (const auto & boundary : boundaries) { + std::cout << " Boundary of " << boundary.size() << " points: "; + for (const auto & point : boundary) { + std::cout << point << ", "; } std::cout << std::endl; } @@ -625,18 +621,18 @@ void TriContourGenerator::clear_visited_flags(bool include_boundaries) // Initialise _boundaries_visited. _boundaries_visited.reserve(boundaries.size()); - for (Boundaries::const_iterator it = boundaries.begin(); - it != boundaries.end(); ++it) - _boundaries_visited.push_back(BoundaryVisited(it->size())); + for (const auto & boundary : boundaries) { + _boundaries_visited.emplace_back(boundary.size()); + } // Initialise _boundaries_used. _boundaries_used = BoundariesUsed(boundaries.size()); } // Clear _boundaries_visited. - for (BoundariesVisited::iterator it = _boundaries_visited.begin(); - it != _boundaries_visited.end(); ++it) - std::fill(it->begin(), it->end(), false); + for (auto & it : _boundaries_visited) { + std::fill(it.begin(), it.end(), false); + } // Clear _boundaries_used. std::fill(_boundaries_used.begin(), _boundaries_used.end(), false); @@ -672,11 +668,13 @@ py::tuple TriContourGenerator::contour_line_to_segs_and_kinds(const Contour& con CodeArray codes(codes_dims); unsigned char* codes_ptr = codes.mutable_data(); - for (ContourLine::const_iterator it = contour_line.begin(); - it != contour_line.end(); ++it) { - *segs_ptr++ = it->x; - *segs_ptr++ = it->y; - *codes_ptr++ = (it == contour_line.begin() ? MOVETO : LINETO); + for (const auto & point : contour_line) { + *segs_ptr++ = point.x; + *segs_ptr++ = point.y; + *codes_ptr++ = LINETO; + } + if (npoints > 0) { + *codes.mutable_data(0) = MOVETO; } // Closed line loop has identical first and last (x, y) points. @@ -707,13 +705,11 @@ py::tuple TriContourGenerator::contour_to_segs_and_kinds(const Contour& contour) // and they are returned in the Python lists vertices_list and codes_list // respectively. - Contour::const_iterator line; - ContourLine::const_iterator point; - // Find total number of points in all contour lines. py::ssize_t n_points = 0; - for (line = contour.begin(); line != contour.end(); ++line) - n_points += static_cast(line->size()); + for (const auto & line : contour) { + n_points += static_cast(line.size()); + } // Create segs array for point coordinates. py::ssize_t segs_dims[2] = {n_points, 2}; @@ -725,15 +721,16 @@ py::tuple TriContourGenerator::contour_to_segs_and_kinds(const Contour& contour) CodeArray codes(codes_dims); unsigned char* codes_ptr = codes.mutable_data(); - for (line = contour.begin(); line != contour.end(); ++line) { - for (point = line->begin(); point != line->end(); point++) { + for (const auto & line : contour) { + for (auto point = line.cbegin(); point != line.cend(); point++) { *segs_ptr++ = point->x; *segs_ptr++ = point->y; - *codes_ptr++ = (point == line->begin() ? MOVETO : LINETO); + *codes_ptr++ = (point == line.cbegin() ? MOVETO : LINETO); } - if (line->size() > 1) + if (line.size() > 1) { *(codes_ptr-1) = CLOSEPOLY; + } } py::list vertices_list(1); @@ -787,13 +784,10 @@ void TriContourGenerator::find_boundary_lines(Contour& contour, // line to its end before continuing. const Triangulation& triang = _triangulation; const Boundaries& boundaries = get_boundaries(); - for (Boundaries::const_iterator it = boundaries.begin(); - it != boundaries.end(); ++it) { - const Boundary& boundary = *it; + for (const auto & boundary : boundaries) { bool startAbove, endAbove = false; - for (Boundary::const_iterator itb = boundary.begin(); - itb != boundary.end(); ++itb) { - if (itb == boundary.begin()) + for (auto itb = boundary.cbegin(); itb != boundary.cend(); ++itb) { + if (itb == boundary.cbegin()) startAbove = get_z(triang.get_triangle_point(*itb)) >= level; else startAbove = endAbove; @@ -802,8 +796,7 @@ void TriContourGenerator::find_boundary_lines(Contour& contour, if (startAbove && !endAbove) { // This boundary edge is the start point for a contour line, // so follow the line. - contour.push_back(ContourLine()); - ContourLine& contour_line = contour.back(); + ContourLine& contour_line = contour.emplace_back(); TriEdge tri_edge = *itb; follow_interior(contour_line, tri_edge, true, level, false); } @@ -836,8 +829,7 @@ void TriContourGenerator::find_boundary_lines_filled(Contour& contour, if (decr_lower || incr_upper) { // Start point for contour line, so follow it. - contour.push_back(ContourLine()); - ContourLine& contour_line = contour.back(); + ContourLine& contour_line = contour.emplace_back(); TriEdge start_tri_edge = boundary[j]; TriEdge tri_edge = start_tri_edge; @@ -865,11 +857,11 @@ void TriContourGenerator::find_boundary_lines_filled(Contour& contour, const Boundary& boundary = boundaries[i]; double z = get_z(triang.get_triangle_point(boundary[0])); if (z >= lower_level && z < upper_level) { - contour.push_back(ContourLine()); - ContourLine& contour_line = contour.back(); - for (Boundary::size_type j = 0; j < boundary.size(); ++j) + ContourLine& contour_line = contour.emplace_back(); + for (auto edge : boundary) { contour_line.push_back(triang.get_point_coords( - triang.get_triangle_point(boundary[j]))); + triang.get_triangle_point(edge))); + } // Close polygon. contour_line.push_back(contour_line.front()); @@ -899,8 +891,7 @@ void TriContourGenerator::find_interior_lines(Contour& contour, continue; // Contour does not pass through this triangle. // Found start of new contour line loop. - contour.push_back(ContourLine()); - ContourLine& contour_line = contour.back(); + ContourLine& contour_line = contour.emplace_back(); TriEdge tri_edge = triang.get_neighbor_edge(tri, edge); follow_interior(contour_line, tri_edge, false, level, on_upper); @@ -1073,8 +1064,8 @@ XY TriContourGenerator::interp(int point1, TrapezoidMapTriFinder::TrapezoidMapTriFinder(Triangulation& triangulation) : _triangulation(triangulation), - _points(0), - _tree(0) + _points(nullptr), + _tree(nullptr) {} TrapezoidMapTriFinder::~TrapezoidMapTriFinder() @@ -1092,9 +1083,9 @@ TrapezoidMapTriFinder::add_edge_to_tree(const Edge& edge) const Point* p = edge.left; const Point* q = edge.right; - Trapezoid* left_old = 0; // old trapezoid to the left. - Trapezoid* left_below = 0; // below trapezoid to the left. - Trapezoid* left_above = 0; // above trapezoid to the left. + Trapezoid* left_old = nullptr; // old trapezoid to the left. + Trapezoid* left_below = nullptr; // below trapezoid to the left. + Trapezoid* left_above = nullptr; // above trapezoid to the left. // Iterate through trapezoids intersecting edge from left to right. // Replace each old trapezoid with 2+ new trapezoids, and replace its @@ -1110,10 +1101,10 @@ TrapezoidMapTriFinder::add_edge_to_tree(const Edge& edge) // Old trapezoid is replaced by up to 4 new trapezoids: left is to the // left of the start point p, below/above are below/above the edge // inserted, and right is to the right of the end point q. - Trapezoid* left = 0; - Trapezoid* below = 0; - Trapezoid* above = 0; - Trapezoid* right = 0; + Trapezoid* left = nullptr; + Trapezoid* below = nullptr; + Trapezoid* above = nullptr; + Trapezoid* right = nullptr; // There are 4 different cases here depending on whether the old // trapezoid in question is the start and/or end trapezoid of those @@ -1301,12 +1292,12 @@ void TrapezoidMapTriFinder::clear() { delete [] _points; - _points = 0; + _points = nullptr; _edges.clear(); delete _tree; - _tree = 0; + _tree = nullptr; } TrapezoidMapTriFinder::TriIndexArray @@ -1335,7 +1326,7 @@ int TrapezoidMapTriFinder::find_one(const XY& xy) { const Node* node = _tree->search(xy); - assert(node != 0 && "Search tree for point returned null node"); + assert(node != nullptr && "Search tree for point returned null node"); return node->get_tri(); } @@ -1348,8 +1339,8 @@ TrapezoidMapTriFinder::find_trapezoids_intersecting_edge( // checks to deal with simple colinear (i.e. invalid) triangles. trapezoids.clear(); Trapezoid* trapezoid = _tree->search(edge); - if (trapezoid == 0) { - assert(trapezoid != 0 && "search(edge) returns null trapezoid"); + if (trapezoid == nullptr) { + assert(trapezoid != nullptr && "search(edge) returns null trapezoid"); return false; } @@ -1372,7 +1363,7 @@ TrapezoidMapTriFinder::find_trapezoids_intersecting_edge( else if (orient == +1) trapezoid = trapezoid->upper_right; - if (trapezoid == 0) { + if (trapezoid == nullptr) { assert(0 && "Expected trapezoid neighbor"); return false; } @@ -1439,8 +1430,10 @@ TrapezoidMapTriFinder::initialize() // Set up edges array. // First the bottom and top edges of the enclosing rectangle. - _edges.push_back(Edge(&_points[npoints], &_points[npoints+1],-1,-1,0,0)); - _edges.push_back(Edge(&_points[npoints+2],&_points[npoints+3],-1,-1,0,0)); + _edges.emplace_back(&_points[npoints], &_points[npoints+1], -1, -1, + nullptr, nullptr); + _edges.emplace_back(&_points[npoints+2], &_points[npoints+3], -1, -1, + nullptr, nullptr); // Add all edges in the triangulation that point to the right. Do not // explicitly include edges that point to the left as the neighboring @@ -1457,13 +1450,14 @@ TrapezoidMapTriFinder::initialize() TriEdge neighbor = triang.get_neighbor_edge(tri,edge); if (end->is_right_of(*start)) { const Point* neighbor_point_below = (neighbor.tri == -1) ? - 0 : _points + triang.get_triangle_point( + nullptr : _points + triang.get_triangle_point( neighbor.tri, (neighbor.edge+2)%3); - _edges.push_back(Edge(start, end, neighbor.tri, tri, - neighbor_point_below, other)); + _edges.emplace_back(start, end, neighbor.tri, tri, + neighbor_point_below, other); + } + else if (neighbor.tri == -1) { + _edges.emplace_back(end, start, tri, -1, other, nullptr); } - else if (neighbor.tri == -1) - _edges.push_back(Edge(end, start, tri, -1, other, 0)); // Set triangle associated with start point if not already set. if (start->tri == -1) @@ -1493,7 +1487,7 @@ TrapezoidMapTriFinder::initialize() void TrapezoidMapTriFinder::print_tree() { - assert(_tree != 0 && "Null Node tree"); + assert(_tree != nullptr && "Null Node tree"); _tree->print(); } @@ -1510,8 +1504,8 @@ TrapezoidMapTriFinder::Edge::Edge(const Point* left_, point_below(point_below_), point_above(point_above_) { - assert(left != 0 && "Null left point"); - assert(right != 0 && "Null right point"); + assert(left != nullptr && "Null left point"); + assert(right != nullptr && "Null right point"); assert(right->is_right_of(*left) && "Incorrect point order"); assert(triangle_below >= -1 && "Invalid triangle below index"); assert(triangle_above >= -1 && "Invalid triangle above index"); @@ -1552,7 +1546,7 @@ TrapezoidMapTriFinder::Edge::get_y_at_x(const double& x) const bool TrapezoidMapTriFinder::Edge::has_point(const Point* point) const { - assert(point != 0 && "Null point"); + assert(point != nullptr && "Null point"); return (left == point || right == point); } @@ -1572,9 +1566,9 @@ TrapezoidMapTriFinder::Edge::print_debug() const TrapezoidMapTriFinder::Node::Node(const Point* point, Node* left, Node* right) : _type(Type_XNode) { - assert(point != 0 && "Invalid point"); - assert(left != 0 && "Invalid left node"); - assert(right != 0 && "Invalid right node"); + assert(point != nullptr && "Invalid point"); + assert(left != nullptr && "Invalid left node"); + assert(right != nullptr && "Invalid right node"); _union.xnode.point = point; _union.xnode.left = left; _union.xnode.right = right; @@ -1585,9 +1579,9 @@ TrapezoidMapTriFinder::Node::Node(const Point* point, Node* left, Node* right) TrapezoidMapTriFinder::Node::Node(const Edge* edge, Node* below, Node* above) : _type(Type_YNode) { - assert(edge != 0 && "Invalid edge"); - assert(below != 0 && "Invalid below node"); - assert(above != 0 && "Invalid above node"); + assert(edge != nullptr && "Invalid edge"); + assert(below != nullptr && "Invalid below node"); + assert(above != nullptr && "Invalid above node"); _union.ynode.edge = edge; _union.ynode.below = below; _union.ynode.above = above; @@ -1598,7 +1592,7 @@ TrapezoidMapTriFinder::Node::Node(const Edge* edge, Node* below, Node* above) TrapezoidMapTriFinder::Node::Node(Trapezoid* trapezoid) : _type(Type_TrapezoidNode) { - assert(trapezoid != 0 && "Null Trapezoid"); + assert(trapezoid != nullptr && "Null Trapezoid"); _union.trapezoid = trapezoid; trapezoid->trapezoid_node = this; } @@ -1627,7 +1621,7 @@ TrapezoidMapTriFinder::Node::~Node() void TrapezoidMapTriFinder::Node::add_parent(Node* parent) { - assert(parent != 0 && "Null parent"); + assert(parent != nullptr && "Null parent"); assert(parent != this && "Cannot be parent of self"); assert(!has_parent(parent) && "Parent already in collection"); _parents.push_back(parent); @@ -1638,9 +1632,7 @@ TrapezoidMapTriFinder::Node::assert_valid(bool tree_complete) const { #ifndef NDEBUG // Check parents. - for (Parents::const_iterator it = _parents.begin(); - it != _parents.end(); ++it) { - Node* parent = *it; + for (const auto & parent : _parents) { assert(parent != this && "Cannot be parent of self"); assert(parent->has_child(this) && "Parent missing child"); } @@ -1648,23 +1640,23 @@ TrapezoidMapTriFinder::Node::assert_valid(bool tree_complete) const // Check children, and recurse. switch (_type) { case Type_XNode: - assert(_union.xnode.left != 0 && "Null left child"); + assert(_union.xnode.left != nullptr && "Null left child"); assert(_union.xnode.left->has_parent(this) && "Incorrect parent"); - assert(_union.xnode.right != 0 && "Null right child"); + assert(_union.xnode.right != nullptr && "Null right child"); assert(_union.xnode.right->has_parent(this) && "Incorrect parent"); _union.xnode.left->assert_valid(tree_complete); _union.xnode.right->assert_valid(tree_complete); break; case Type_YNode: - assert(_union.ynode.below != 0 && "Null below child"); + assert(_union.ynode.below != nullptr && "Null below child"); assert(_union.ynode.below->has_parent(this) && "Incorrect parent"); - assert(_union.ynode.above != 0 && "Null above child"); + assert(_union.ynode.above != nullptr && "Null above child"); assert(_union.ynode.above->has_parent(this) && "Incorrect parent"); _union.ynode.below->assert_valid(tree_complete); _union.ynode.above->assert_valid(tree_complete); break; case Type_TrapezoidNode: - assert(_union.trapezoid != 0 && "Null trapezoid"); + assert(_union.trapezoid != nullptr && "Null trapezoid"); assert(_union.trapezoid->trapezoid_node == this && "Incorrect trapezoid node"); _union.trapezoid->assert_valid(tree_complete); @@ -1724,7 +1716,7 @@ TrapezoidMapTriFinder::Node::get_tri() const bool TrapezoidMapTriFinder::Node::has_child(const Node* child) const { - assert(child != 0 && "Null child node"); + assert(child != nullptr && "Null child node"); switch (_type) { case Type_XNode: return (_union.xnode.left == child || _union.xnode.right == child); @@ -1777,9 +1769,9 @@ TrapezoidMapTriFinder::Node::print(int depth /* = 0 */) const bool TrapezoidMapTriFinder::Node::remove_parent(Node* parent) { - assert(parent != 0 && "Null parent"); + assert(parent != nullptr && "Null parent"); assert(parent != this && "Cannot be parent of self"); - Parents::iterator it = std::find(_parents.begin(), _parents.end(), parent); + auto it = std::find(_parents.begin(), _parents.end(), parent); assert(it != _parents.end() && "Parent not in collection"); _parents.erase(it); return _parents.empty(); @@ -1792,7 +1784,7 @@ TrapezoidMapTriFinder::Node::replace_child(Node* old_child, Node* new_child) case Type_XNode: assert((_union.xnode.left == old_child || _union.xnode.right == old_child) && "Not a child Node"); - assert(new_child != 0 && "Null child node"); + assert(new_child != nullptr && "Null child node"); if (_union.xnode.left == old_child) _union.xnode.left = new_child; else @@ -1801,7 +1793,7 @@ TrapezoidMapTriFinder::Node::replace_child(Node* old_child, Node* new_child) case Type_YNode: assert((_union.ynode.below == old_child || _union.ynode.above == old_child) && "Not a child node"); - assert(new_child != 0 && "Null child node"); + assert(new_child != nullptr && "Null child node"); if (_union.ynode.below == old_child) _union.ynode.below = new_child; else @@ -1818,7 +1810,7 @@ TrapezoidMapTriFinder::Node::replace_child(Node* old_child, Node* new_child) void TrapezoidMapTriFinder::Node::replace_with(Node* new_node) { - assert(new_node != 0 && "Null replacement node"); + assert(new_node != nullptr && "Null replacement node"); // Replace child of each parent with new_node. As each has parent has its // child replaced it is removed from the _parents collection. while (!_parents.empty()) @@ -1876,7 +1868,7 @@ TrapezoidMapTriFinder::Node::search(const Edge& edge) else { assert(0 && "Invalid triangulation, common left points"); - return 0; + return nullptr; } } if (edge.get_slope() > _union.ynode.edge->get_slope()) @@ -1896,7 +1888,7 @@ TrapezoidMapTriFinder::Node::search(const Edge& edge) else { assert(0 && "Invalid triangulation, common right points"); - return 0; + return nullptr; } } if (edge.get_slope() > _union.ynode.edge->get_slope()) @@ -1909,15 +1901,15 @@ TrapezoidMapTriFinder::Node::search(const Edge& edge) _union.ynode.edge->get_point_orientation(*edge.left); if (orient == 0) { // edge.left lies on _union.ynode.edge - if (_union.ynode.edge->point_above != 0 && + if (_union.ynode.edge->point_above != nullptr && edge.has_point(_union.ynode.edge->point_above)) orient = -1; - else if (_union.ynode.edge->point_below != 0 && + else if (_union.ynode.edge->point_below != nullptr && edge.has_point(_union.ynode.edge->point_below)) orient = +1; else { assert(0 && "Invalid triangulation, point on edge"); - return 0; + return nullptr; } } if (orient < 0) @@ -1935,11 +1927,12 @@ TrapezoidMapTriFinder::Trapezoid::Trapezoid(const Point* left_, const Edge& below_, const Edge& above_) : left(left_), right(right_), below(below_), above(above_), - lower_left(0), lower_right(0), upper_left(0), upper_right(0), - trapezoid_node(0) + lower_left(nullptr), lower_right(nullptr), + upper_left(nullptr), upper_right(nullptr), + trapezoid_node(nullptr) { - assert(left != 0 && "Null left point"); - assert(right != 0 && "Null right point"); + assert(left != nullptr && "Null left point"); + assert(right != nullptr && "Null right point"); assert(right->is_right_of(*left) && "Incorrect point order"); } @@ -1947,10 +1940,10 @@ void TrapezoidMapTriFinder::Trapezoid::assert_valid(bool tree_complete) const { #ifndef NDEBUG - assert(left != 0 && "Null left point"); - assert(right != 0 && "Null right point"); + assert(left != nullptr && "Null left point"); + assert(right != nullptr && "Null right point"); - if (lower_left != 0) { + if (lower_left != nullptr) { assert(lower_left->below == below && lower_left->lower_right == this && "Incorrect lower_left trapezoid"); @@ -1958,7 +1951,7 @@ TrapezoidMapTriFinder::Trapezoid::assert_valid(bool tree_complete) const "Incorrect lower left point"); } - if (lower_right != 0) { + if (lower_right != nullptr) { assert(lower_right->below == below && lower_right->lower_left == this && "Incorrect lower_right trapezoid"); @@ -1966,7 +1959,7 @@ TrapezoidMapTriFinder::Trapezoid::assert_valid(bool tree_complete) const "Incorrect lower right point"); } - if (upper_left != 0) { + if (upper_left != nullptr) { assert(upper_left->above == above && upper_left->upper_right == this && "Incorrect upper_left trapezoid"); @@ -1974,7 +1967,7 @@ TrapezoidMapTriFinder::Trapezoid::assert_valid(bool tree_complete) const "Incorrect upper left point"); } - if (upper_right != 0) { + if (upper_right != nullptr) { assert(upper_right->above == above && upper_right->upper_left == this && "Incorrect upper_right trapezoid"); @@ -1982,7 +1975,7 @@ TrapezoidMapTriFinder::Trapezoid::assert_valid(bool tree_complete) const "Incorrect upper right point"); } - assert(trapezoid_node != 0 && "Null trapezoid_node"); + assert(trapezoid_node != nullptr && "Null trapezoid_node"); if (tree_complete) { assert(below.triangle_above == above.triangle_below && @@ -2042,30 +2035,34 @@ void TrapezoidMapTriFinder::Trapezoid::set_lower_left(Trapezoid* lower_left_) { lower_left = lower_left_; - if (lower_left != 0) + if (lower_left != nullptr) { lower_left->lower_right = this; + } } void TrapezoidMapTriFinder::Trapezoid::set_lower_right(Trapezoid* lower_right_) { lower_right = lower_right_; - if (lower_right != 0) + if (lower_right != nullptr) { lower_right->lower_left = this; + } } void TrapezoidMapTriFinder::Trapezoid::set_upper_left(Trapezoid* upper_left_) { upper_left = upper_left_; - if (upper_left != 0) + if (upper_left != nullptr) { upper_left->upper_right = this; + } } void TrapezoidMapTriFinder::Trapezoid::set_upper_right(Trapezoid* upper_right_) { upper_right = upper_right_; - if (upper_right != 0) + if (upper_right != nullptr) { upper_right->upper_left = this; + } } diff --git a/src/tri/_tri.h b/src/tri/_tri.h index 2319650b367b..994b1f43c556 100644 --- a/src/tri/_tri.h +++ b/src/tri/_tri.h @@ -75,7 +75,7 @@ namespace py = pybind11; -/* An edge of a triangle consisting of an triangle index in the range 0 to +/* An edge of a triangle consisting of a triangle index in the range 0 to * ntri-1 and an edge index in the range 0 to 2. Edge i goes from the * triangle's point i to point (i+1)%3. */ struct TriEdge final diff --git a/subprojects/packagefiles/freetype-2.6.1-meson/meson.build b/subprojects/packagefiles/freetype-2.6.1-meson/meson.build index 9a5180ef7586..1fd4bc44e7b5 100644 --- a/subprojects/packagefiles/freetype-2.6.1-meson/meson.build +++ b/subprojects/packagefiles/freetype-2.6.1-meson/meson.build @@ -179,11 +179,17 @@ ft_config_headers += [configure_file(input: 'include/freetype/config/ftoption.h. output: 'ftoption.h', configuration: conf)] +if cc.get_id() == 'emscripten' + kwargs = {} +else + kwargs = {'gnu_symbol_visibility': 'inlineshidden'} +endif + libfreetype = static_library('freetype', base_sources, include_directories: incbase, dependencies: deps, c_args: c_args, - gnu_symbol_visibility: 'inlineshidden', + kwargs: kwargs ) freetype_dep = declare_dependency( diff --git a/subprojects/packagefiles/freetype-2.6.1-meson/src/gzip/zconf.h b/subprojects/packagefiles/freetype-2.6.1-meson/src/gzip/zconf.h new file mode 100644 index 000000000000..d88a82a2eec8 --- /dev/null +++ b/subprojects/packagefiles/freetype-2.6.1-meson/src/gzip/zconf.h @@ -0,0 +1,284 @@ +/* zconf.h -- configuration of the zlib compression library + * Copyright (C) 1995-2002 Jean-loup Gailly. + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* @(#) $Id$ */ + +#ifndef _ZCONF_H +#define _ZCONF_H + +/* + * If you *really* need a unique prefix for all types and library functions, + * compile with -DZ_PREFIX. The "standard" zlib should be compiled without it. + */ +#ifdef Z_PREFIX +# define deflateInit_ z_deflateInit_ +# define deflate z_deflate +# define deflateEnd z_deflateEnd +# define inflateInit_ z_inflateInit_ +# define inflate z_inflate +# define inflateEnd z_inflateEnd +# define deflateInit2_ z_deflateInit2_ +# define deflateSetDictionary z_deflateSetDictionary +# define deflateCopy z_deflateCopy +# define deflateReset z_deflateReset +# define deflateParams z_deflateParams +# define inflateInit2_ z_inflateInit2_ +# define inflateSetDictionary z_inflateSetDictionary +# define inflateSync z_inflateSync +# define inflateSyncPoint z_inflateSyncPoint +# define inflateReset z_inflateReset +# define compress z_compress +# define compress2 z_compress2 +# define uncompress z_uncompress +# define adler32 z_adler32 +# define crc32 z_crc32 +# define get_crc_table z_get_crc_table + +# define Byte z_Byte +# define uInt z_uInt +# define uLong z_uLong +# define Bytef z_Bytef +# define charf z_charf +# define intf z_intf +# define uIntf z_uIntf +# define uLongf z_uLongf +# define voidpf z_voidpf +# define voidp z_voidp +#endif + +#if (defined(_WIN32) || defined(__WIN32__)) && !defined(WIN32) +# define WIN32 +#endif +#if defined(__GNUC__) || defined(WIN32) || defined(__386__) || defined(i386) +# ifndef __32BIT__ +# define __32BIT__ +# endif +#endif +#if defined(__MSDOS__) && !defined(MSDOS) +# define MSDOS +#endif + +/* WinCE doesn't have errno.h */ +#ifdef _WIN32_WCE +# define NO_ERRNO_H +#endif + + +/* + * Compile with -DMAXSEG_64K if the alloc function cannot allocate more + * than 64k bytes at a time (needed on systems with 16-bit int). + */ +#if defined(MSDOS) && !defined(__32BIT__) +# define MAXSEG_64K +#endif +#ifdef MSDOS +# define UNALIGNED_OK +#endif + +#if (defined(MSDOS) || defined(_WINDOWS) || defined(WIN32)) && !defined(STDC) +# define STDC +#endif +#if defined(__STDC__) || defined(__cplusplus) || defined(__OS2__) +# ifndef STDC +# define STDC +# endif +#endif + +#ifndef STDC +# ifndef const /* cannot use !defined(STDC) && !defined(const) on Mac */ +# define const +# endif +#endif + +/* Some Mac compilers merge all .h files incorrectly: */ +#if defined(__MWERKS__) || defined(applec) ||defined(THINK_C) ||defined(__SC__) +# define NO_DUMMY_DECL +#endif + +/* Old Borland C and LCC incorrectly complains about missing returns: */ +#if defined(__BORLANDC__) && (__BORLANDC__ < 0x500) +# define NEED_DUMMY_RETURN +#endif + +#if defined(__LCC__) +# define NEED_DUMMY_RETURN +#endif + +/* Maximum value for memLevel in deflateInit2 */ +#ifndef MAX_MEM_LEVEL +# ifdef MAXSEG_64K +# define MAX_MEM_LEVEL 8 +# else +# define MAX_MEM_LEVEL 9 +# endif +#endif + +/* Maximum value for windowBits in deflateInit2 and inflateInit2. + * WARNING: reducing MAX_WBITS makes minigzip unable to extract .gz files + * created by gzip. (Files created by minigzip can still be extracted by + * gzip.) + */ +#ifndef MAX_WBITS +# define MAX_WBITS 15 /* 32K LZ77 window */ +#endif + +/* The memory requirements for deflate are (in bytes): + (1 << (windowBits+2)) + (1 << (memLevel+9)) + that is: 128K for windowBits=15 + 128K for memLevel = 8 (default values) + plus a few kilobytes for small objects. For example, if you want to reduce + the default memory requirements from 256K to 128K, compile with + make CFLAGS="-O -DMAX_WBITS=14 -DMAX_MEM_LEVEL=7" + Of course this will generally degrade compression (there's no free lunch). + + The memory requirements for inflate are (in bytes) 1 << windowBits + that is, 32K for windowBits=15 (default value) plus a few kilobytes + for small objects. +*/ + + /* Type declarations */ + +#ifndef OF /* function prototypes */ +# ifdef STDC +# define OF(args) args +# else +# define OF(args) () +# endif +#endif + +/* The following definitions for FAR are needed only for MSDOS mixed + * model programming (small or medium model with some far allocations). + * This was tested only with MSC; for other MSDOS compilers you may have + * to define NO_MEMCPY in zutil.h. If you don't need the mixed model, + * just define FAR to be empty. + */ +#if (defined(M_I86SM) || defined(M_I86MM)) && !defined(__32BIT__) + /* MSC small or medium model */ +# define SMALL_MEDIUM +# ifdef _MSC_VER +# define FAR _far +# else +# define FAR far +# endif +#endif +#if defined(__BORLANDC__) && (defined(__SMALL__) || defined(__MEDIUM__)) +# ifndef __32BIT__ +# define SMALL_MEDIUM +# define FAR _far +# endif +#endif + +/* Compile with -DZLIB_DLL for Windows DLL support */ +#if defined(ZLIB_DLL) +# if defined(_WINDOWS) || defined(WINDOWS) +# ifdef FAR +# undef FAR +# endif +# include +# define ZEXPORT(x) x WINAPI +# ifdef WIN32 +# define ZEXPORTVA(x) x WINAPIV +# else +# define ZEXPORTVA(x) x FAR _cdecl _export +# endif +# endif +# if defined (__BORLANDC__) +# if (__BORLANDC__ >= 0x0500) && defined (WIN32) +# include +# define ZEXPORT(x) x __declspec(dllexport) WINAPI +# define ZEXPORTRVA(x) x __declspec(dllexport) WINAPIV +# else +# if defined (_Windows) && defined (__DLL__) +# define ZEXPORT(x) x _export +# define ZEXPORTVA(x) x _export +# endif +# endif +# endif +#endif + + +#ifndef ZEXPORT +# define ZEXPORT(x) static x +#endif +#ifndef ZEXPORTVA +# define ZEXPORTVA(x) static x +#endif +#ifndef ZEXTERN +# define ZEXTERN(x) static x +#endif +#ifndef ZEXTERNDEF +# define ZEXTERNDEF(x) static x +#endif + +#ifndef FAR +# define FAR +#endif + +#if !defined(__MACTYPES__) +typedef unsigned char Byte; /* 8 bits */ +#endif +typedef unsigned int uInt; /* 16 bits or more */ +typedef unsigned long uLong; /* 32 bits or more */ + +#ifdef SMALL_MEDIUM + /* Borland C/C++ and some old MSC versions ignore FAR inside typedef */ +# define Bytef Byte FAR +#else + typedef Byte FAR Bytef; +#endif +typedef char FAR charf; +typedef int FAR intf; +typedef uInt FAR uIntf; +typedef uLong FAR uLongf; + +#ifdef STDC + typedef void FAR *voidpf; + typedef void *voidp; +#else + typedef Byte FAR *voidpf; + typedef Byte *voidp; +#endif + +#ifdef HAVE_UNISTD_H +# include /* for off_t */ +# include /* for SEEK_* and off_t */ +# define z_off_t off_t +#endif +#ifndef SEEK_SET +# define SEEK_SET 0 /* Seek from beginning of file. */ +# define SEEK_CUR 1 /* Seek from current position. */ +# define SEEK_END 2 /* Set file pointer to EOF plus "offset" */ +#endif +#ifndef z_off_t +# define z_off_t long +#endif + +/* MVS linker does not support external names larger than 8 bytes */ +#if defined(__MVS__) +# pragma map(deflateInit_,"DEIN") +# pragma map(deflateInit2_,"DEIN2") +# pragma map(deflateEnd,"DEEND") +# pragma map(inflateInit_,"ININ") +# pragma map(inflateInit2_,"ININ2") +# pragma map(inflateEnd,"INEND") +# pragma map(inflateSync,"INSY") +# pragma map(inflateSetDictionary,"INSEDI") +# pragma map(inflate_blocks,"INBL") +# pragma map(inflate_blocks_new,"INBLNE") +# pragma map(inflate_blocks_free,"INBLFR") +# pragma map(inflate_blocks_reset,"INBLRE") +# pragma map(inflate_codes_free,"INCOFR") +# pragma map(inflate_codes,"INCO") +# pragma map(inflate_fast,"INFA") +# pragma map(inflate_flush,"INFLU") +# pragma map(inflate_mask,"INMA") +# pragma map(inflate_set_dictionary,"INSEDI2") +# pragma map(inflate_copyright,"INCOPY") +# pragma map(inflate_trees_bits,"INTRBI") +# pragma map(inflate_trees_dynamic,"INTRDY") +# pragma map(inflate_trees_fixed,"INTRFI") +# pragma map(inflate_trees_free,"INTRFR") +#endif + +#endif /* _ZCONF_H */ diff --git a/subprojects/packagefiles/qhull-143.patch b/subprojects/packagefiles/qhull-143.patch new file mode 100644 index 000000000000..e37a0d28da91 --- /dev/null +++ b/subprojects/packagefiles/qhull-143.patch @@ -0,0 +1,419 @@ +From cd8c281da87d38820ecc4c452bbf6fd921155915 Mon Sep 17 00:00:00 2001 +From: Elliott Sales de Andrade +Date: Thu, 28 Mar 2024 00:54:59 -0400 +Subject: [PATCH 1/3] Annotate printf-like functions with GCC's format + attribute + +This allows checking format strings when building with `-Wformat` (or +with `-Wall`). +--- + src/libqhull/libqhull.h | 13 ++++++++++--- + src/libqhull_r/libqhull_r.h | 13 ++++++++++--- + src/testqset_r/testqset_r.c | 4 ++-- + 3 files changed, 22 insertions(+), 8 deletions(-) + +diff --git a/src/libqhull/libqhull.h b/src/libqhull/libqhull.h +index 90c0519b..1080ec11 100644 +--- a/src/libqhull/libqhull.h ++++ b/src/libqhull/libqhull.h +@@ -60,6 +60,13 @@ + #endif + #endif + ++#if defined(__GNUC__) ++/* See https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html#index-format-function-attribute */ ++#define QH_PRINTF_LIKE(string_index, first_to_check) __attribute__((format(printf, string_index, first_to_check))) ++#else ++#define QH_PRINTF_LIKE(string_index, first_to_check) ++#endif ++ + /*============ constants and basic types ====================*/ + + extern const char qh_version[]; /* defined in global.c */ +@@ -1136,13 +1143,13 @@ void qh_user_memsizes(void); + + /********* -usermem.c prototypes (alphabetical) **********************/ + void qh_exit(int exitcode); +-void qh_fprintf_stderr(int msgcode, const char *fmt, ... ); ++void qh_fprintf_stderr(int msgcode, const char *fmt, ... ) QH_PRINTF_LIKE(2, 3); + void qh_free(void *mem); + void *qh_malloc(size_t size); + + /********* -userprintf.c and userprintf_rbox.c prototypes **********************/ +-void qh_fprintf(FILE *fp, int msgcode, const char *fmt, ... ); +-void qh_fprintf_rbox(FILE *fp, int msgcode, const char *fmt, ... ); ++void qh_fprintf(FILE *fp, int msgcode, const char *fmt, ... ) QH_PRINTF_LIKE(3, 4); ++void qh_fprintf_rbox(FILE *fp, int msgcode, const char *fmt, ... ) QH_PRINTF_LIKE(3, 4); + + /***** -geom.c/geom2.c/random.c prototypes (duplicated from geom.h, random.h) ****************/ + +diff --git a/src/libqhull_r/libqhull_r.h b/src/libqhull_r/libqhull_r.h +index 023e0181..917f96af 100644 +--- a/src/libqhull_r/libqhull_r.h ++++ b/src/libqhull_r/libqhull_r.h +@@ -48,6 +48,13 @@ + #endif + #endif + ++#if defined(__GNUC__) ++/* See https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html#index-format-function-attribute */ ++#define QH_PRINTF_LIKE(string_index, first_to_check) __attribute__((format(printf, string_index, first_to_check))) ++#else ++#define QH_PRINTF_LIKE(string_index, first_to_check) ++#endif ++ + /*============ constants and basic types ====================*/ + + extern const char qh_version[]; /* defined in global_r.c */ +@@ -1132,13 +1139,13 @@ void qh_user_memsizes(qhT *qh); + + /********* -usermem_r.c prototypes (alphabetical) **********************/ + void qh_exit(int exitcode); +-void qh_fprintf_stderr(int msgcode, const char *fmt, ... ); ++void qh_fprintf_stderr(int msgcode, const char *fmt, ... ) QH_PRINTF_LIKE(2, 3); + void qh_free(void *mem); + void *qh_malloc(size_t size); + + /********* -userprintf_r.c and userprintf_rbox_r.c prototypes **********************/ +-void qh_fprintf(qhT *qh, FILE *fp, int msgcode, const char *fmt, ... ); +-void qh_fprintf_rbox(qhT *qh, FILE *fp, int msgcode, const char *fmt, ... ); ++void qh_fprintf(qhT *qh, FILE *fp, int msgcode, const char *fmt, ... ) QH_PRINTF_LIKE(4, 5); ++void qh_fprintf_rbox(qhT *qh, FILE *fp, int msgcode, const char *fmt, ... ) QH_PRINTF_LIKE(4, 5); + + /***** -geom_r.c/geom2_r.c/random_r.c prototypes (duplicated from geom_r.h, random_r.h) ****************/ + +diff --git a/src/testqset_r/testqset_r.c b/src/testqset_r/testqset_r.c +index 671494f3..b0253e0e 100644 +--- a/src/testqset_r/testqset_r.c ++++ b/src/testqset_r/testqset_r.c +@@ -117,7 +117,7 @@ int error_count= 0; /* Global error_count. checkSetContents(qh) keeps its own + /* Functions normally defined in user_r.h for usermem_r.c */ + + void qh_exit(int exitcode); +-void qh_fprintf_stderr(int msgcode, const char *fmt, ... ); ++void qh_fprintf_stderr(int msgcode, const char *fmt, ... ) QH_PRINTF_LIKE(2, 3); + void qh_free(void *mem); + void *qh_malloc(size_t size); + +@@ -134,7 +134,7 @@ void qh_errexit(qhT *qh, int exitcode, facetT *f, ridgeT *r) + + /* Normally defined in userprintf_r.c */ + +-void qh_fprintf(qhT *qh, FILE *fp, int msgcode, const char *fmt, ... ); ++void qh_fprintf(qhT *qh, FILE *fp, int msgcode, const char *fmt, ... ) QH_PRINTF_LIKE(4, 5); + void qh_fprintf(qhT *qh, FILE *fp, int msgcode, const char *fmt, ... ) + { + static int needs_cr= 0; /* True if qh_fprintf needs a CR. testqset_r is not itself reentrant */ + +From cc7e366259866d4cd24a312a4aaf891ff0abe85a Mon Sep 17 00:00:00 2001 +From: Elliott Sales de Andrade +Date: Thu, 28 Mar 2024 01:05:06 -0400 +Subject: [PATCH 2/3] Fix arguments inconsistent with their format strings + +--- + src/libqhull/global.c | 2 +- + src/libqhull/merge.c | 11 +++++------ + src/libqhull/poly2.c | 5 +++-- + src/libqhull_r/global_r.c | 2 +- + src/libqhull_r/mem_r.c | 4 ++-- + src/libqhull_r/merge_r.c | 11 +++++------ + src/libqhull_r/poly2_r.c | 5 +++-- + src/libqhullcpp/Qhull.cpp | 2 +- + src/testqset_r/testqset_r.c | 14 +++++++------- + 9 files changed, 28 insertions(+), 28 deletions(-) + +diff --git a/src/libqhull/global.c b/src/libqhull/global.c +index 27babbb4..faf67f37 100644 +--- a/src/libqhull/global.c ++++ b/src/libqhull/global.c +@@ -2248,7 +2248,7 @@ void qh_lib_check(int qhullLibraryType, int qhTsize, int vertexTsize, int ridgeT + last_errcode= 6253; + } + if (qhmemTsize && qhmemTsize != sizeof(qhmemT)) { +- qh_fprintf_stderr(6254, "qh_lib_check: Incorrect qhull library called. Size of qhmemT for caller is %d, but for qhull library is %d.\n", qhmemTsize, sizeof(qhmemT)); ++ qh_fprintf_stderr(6254, "qh_lib_check: Incorrect qhull library called. Size of qhmemT for caller is %d, but for qhull library is %d.\n", qhmemTsize, (int)sizeof(qhmemT)); + last_errcode= 6254; + } + if (last_errcode) { +diff --git a/src/libqhull/merge.c b/src/libqhull/merge.c +index de3a0b00..89392dc6 100644 +--- a/src/libqhull/merge.c ++++ b/src/libqhull/merge.c +@@ -427,7 +427,7 @@ void qh_appendmergeset(facetT *facet, facetT *neighbor, mergeType mergetype, coo + return; + } + if (!qh facet_mergeset || !qh degen_mergeset) { +- qh_fprintf(qh ferr, 6403, "qhull internal error (qh_appendmergeset): expecting temp set defined for qh.facet_mergeset (0x%x) and qh.degen_mergeset (0x%x). Got NULL\n", ++ qh_fprintf(qh ferr, 6403, "qhull internal error (qh_appendmergeset): expecting temp set defined for qh.facet_mergeset (%p) and qh.degen_mergeset (%p). Got NULL\n", + qh facet_mergeset, qh degen_mergeset); + /* otherwise qh_setappend creates a new set that is not freed by qh_freebuild() */ + qh_errexit(qh_ERRqhull, NULL, NULL); +@@ -513,8 +513,7 @@ void qh_appendvertexmerge(vertexT *vertex, vertexT *destination, mergeType merge + const char *mergename; + + if (!qh vertex_mergeset) { +- qh_fprintf(qh ferr, 6387, "qhull internal error (qh_appendvertexmerge): expecting temp set defined for qh.vertex_mergeset (0x%x). Got NULL\n", +- qh vertex_mergeset); ++ qh_fprintf(qh ferr, 6387, "qhull internal error (qh_appendvertexmerge): expecting temp set defined for qh.vertex_mergeset. Got NULL\n"); + /* otherwise qh_setappend creates a new set that is not freed by qh_freebuild() */ + qh_errexit(qh_ERRqhull, NULL, NULL); + } +@@ -1654,7 +1653,7 @@ void qh_forcedmerges(boolT *wasmerge) { + void qh_freemergesets(void) { + + if (!qh facet_mergeset || !qh degen_mergeset || !qh vertex_mergeset) { +- qh_fprintf(qh ferr, 6388, "qhull internal error (qh_freemergesets): expecting mergesets. Got a NULL mergeset, qh.facet_mergeset (0x%x), qh.degen_mergeset (0x%x), qh.vertex_mergeset (0x%x)\n", ++ qh_fprintf(qh ferr, 6388, "qhull internal error (qh_freemergesets): expecting mergesets. Got a NULL mergeset, qh.facet_mergeset (%p), qh.degen_mergeset (%p), qh.vertex_mergeset (%p)\n", + qh facet_mergeset, qh degen_mergeset, qh vertex_mergeset); + qh_errexit(qh_ERRqhull, NULL, NULL); + } +@@ -2034,7 +2033,7 @@ ridgeT *qh_hashridge_find(setT *hashtable, int hashsize, ridgeT *ridge, + void qh_initmergesets(void /* qh.facet_mergeset,degen_mergeset,vertex_mergeset */) { + + if (qh facet_mergeset || qh degen_mergeset || qh vertex_mergeset) { +- qh_fprintf(qh ferr, 6386, "qhull internal error (qh_initmergesets): expecting NULL mergesets. Got qh.facet_mergeset (0x%x), qh.degen_mergeset (0x%x), qh.vertex_mergeset (0x%x)\n", ++ qh_fprintf(qh ferr, 6386, "qhull internal error (qh_initmergesets): expecting NULL mergesets. Got qh.facet_mergeset (%p), qh.degen_mergeset (%p), qh.vertex_mergeset (%p)\n", + qh facet_mergeset, qh degen_mergeset, qh vertex_mergeset); + qh_errexit(qh_ERRqhull, NULL, NULL); + } +@@ -2304,7 +2303,7 @@ void qh_maybe_duplicateridge(ridgeT *ridgeA) { + if (k == last) { + vertex= qh_findbest_ridgevertex(ridge, &pinched, &dist); + trace2((qh ferr, 2069, "qh_maybe_duplicateridge: will merge v%d into v%d (dist %2.2g) due to duplicate ridges r%d/r%d with the same vertices. mergevertex set\n", +- pinched->id, vertex->id, dist, ridgeA->id, ridge->id, ridgeA->top->id, ridgeA->bottom->id, ridge->top->id, ridge->bottom->id)); ++ pinched->id, vertex->id, dist, ridgeA->id, ridge->id)); + qh_appendvertexmerge(pinched, vertex, MRGvertices, dist, ridgeA, ridge); + ridge->mergevertex= True; /* disables check for duplicate vertices in qh_checkfacet */ + ridgeA->mergevertex= True; +diff --git a/src/libqhull/poly2.c b/src/libqhull/poly2.c +index 0bdfa6d6..9077b9c3 100644 +--- a/src/libqhull/poly2.c ++++ b/src/libqhull/poly2.c +@@ -1144,7 +1144,7 @@ boolT qh_checklists(facetT *facetlist) { + vertex->visitid= qh vertex_visit; + if (vertex->previous != previousvertex) { + qh_fprintf(qh ferr, 6427, "qhull internal error (qh_checklists): expecting v%d.previous == v%d. Got v%d\n", +- vertex->id, previousvertex, getid_(vertex->previous)); ++ vertex->id, previousvertex->id, getid_(vertex->previous)); + waserror= True; + errorvertex= vertex; + } +@@ -2279,7 +2279,8 @@ void qh_initialhull(setT *vertices) { + zzinc_(Zdistcheck); + qh_distplane(qh interior_point, facet, &dist); /* duplicates qh_setfacetplane */ + if (dist > qh DISTround) { /* clearly flipped, due to axis-parallel facet or coplanar firstfacet */ +- trace1((qh ferr, 1031, "qh_initialhull: initial orientation incorrect, qh.interior_point is %2.2g from f%d. Either axis-parallel facet or coplanar firstfacet f%d. Force outside orientation of all facets\n")); ++ trace1((qh ferr, 1031, "qh_initialhull: initial orientation incorrect, qh.interior_point is %2.2g from f%d. Either axis-parallel facet or coplanar firstfacet f%d. Force outside orientation of all facets\n", ++ dist, facet->id, firstfacet->id)); + FORALLfacets { /* reuse facet, then 'break' */ + facet->flipped= False; + facet->toporient ^= (unsigned char)True; +diff --git a/src/libqhull_r/global_r.c b/src/libqhull_r/global_r.c +index 3a0b9c62..c681a715 100644 +--- a/src/libqhull_r/global_r.c ++++ b/src/libqhull_r/global_r.c +@@ -2201,7 +2201,7 @@ void qh_lib_check(int qhullLibraryType, int qhTsize, int vertexTsize, int ridgeT + last_errcode= 6253; + } + if (qhmemTsize && qhmemTsize != sizeof(qhmemT)) { +- qh_fprintf_stderr(6254, "qh_lib_check: Incorrect qhull library called. Size of qhmemT for caller is %d, but for qhull library is %d.\n", qhmemTsize, sizeof(qhmemT)); ++ qh_fprintf_stderr(6254, "qh_lib_check: Incorrect qhull library called. Size of qhmemT for caller is %d, but for qhull library is %d.\n", qhmemTsize, (int)sizeof(qhmemT)); + last_errcode= 6254; + } + if (last_errcode) { +diff --git a/src/libqhull_r/mem_r.c b/src/libqhull_r/mem_r.c +index 7d5509eb..d811f733 100644 +--- a/src/libqhull_r/mem_r.c ++++ b/src/libqhull_r/mem_r.c +@@ -186,7 +186,7 @@ void qh_memcheck(qhT *qh) { + qh_exit(qhmem_ERRqhull); /* can not use qh_errexit() */ + } + if (qh->qhmem.ferr == 0 || qh->qhmem.IStracing < 0 || qh->qhmem.IStracing > 10 || (((qh->qhmem.ALIGNmask+1) & qh->qhmem.ALIGNmask) != 0)) { +- qh_fprintf_stderr(6244, "qhull internal error (qh_memcheck): either qh->qhmem is overwritten or qh->qhmem is not initialized. Call qh_meminit or qh_new_qhull before calling qh_mem routines. ferr 0x%x, IsTracing %d, ALIGNmask 0x%x\n", ++ qh_fprintf_stderr(6244, "qhull internal error (qh_memcheck): either qh->qhmem is overwritten or qh->qhmem is not initialized. Call qh_meminit or qh_new_qhull before calling qh_mem routines. ferr %p, IsTracing %d, ALIGNmask 0x%x\n", + qh->qhmem.ferr, qh->qhmem.IStracing, qh->qhmem.ALIGNmask); + qh_exit(qhmem_ERRqhull); /* can not use qh_errexit() */ + } +@@ -203,7 +203,7 @@ void qh_memcheck(qhT *qh) { + qh_errexit(qh, qhmem_ERRqhull, NULL, NULL); + } + if (qh->qhmem.IStracing != 0) +- qh_fprintf(qh, qh->qhmem.ferr, 8144, "qh_memcheck: total size of freelists totfree is the same as qh->qhmem.totfree\n", totfree); ++ qh_fprintf(qh, qh->qhmem.ferr, 8144, "qh_memcheck: total size of freelists totfree (%d) is the same as qh->qhmem.totfree\n", totfree); + } /* memcheck */ + + /*-facet_mergeset || !qh->degen_mergeset) { +- qh_fprintf(qh, qh->ferr, 6403, "qhull internal error (qh_appendmergeset): expecting temp set defined for qh.facet_mergeset (0x%x) and qh.degen_mergeset (0x%x). Got NULL\n", ++ qh_fprintf(qh, qh->ferr, 6403, "qhull internal error (qh_appendmergeset): expecting temp set defined for qh.facet_mergeset (%p) and qh.degen_mergeset (%p). Got NULL\n", + qh->facet_mergeset, qh->degen_mergeset); + /* otherwise qh_setappend creates a new set that is not freed by qh_freebuild() */ + qh_errexit(qh, qh_ERRqhull, NULL, NULL); +@@ -513,8 +513,7 @@ void qh_appendvertexmerge(qhT *qh, vertexT *vertex, vertexT *destination, mergeT + const char *mergename; + + if (!qh->vertex_mergeset) { +- qh_fprintf(qh, qh->ferr, 6387, "qhull internal error (qh_appendvertexmerge): expecting temp set defined for qh.vertex_mergeset (0x%x). Got NULL\n", +- qh->vertex_mergeset); ++ qh_fprintf(qh, qh->ferr, 6387, "qhull internal error (qh_appendvertexmerge): expecting temp set defined for qh.vertex_mergeset. Got NULL\n"); + /* otherwise qh_setappend creates a new set that is not freed by qh_freebuild() */ + qh_errexit(qh, qh_ERRqhull, NULL, NULL); + } +@@ -1654,7 +1653,7 @@ void qh_forcedmerges(qhT *qh, boolT *wasmerge) { + void qh_freemergesets(qhT *qh) { + + if (!qh->facet_mergeset || !qh->degen_mergeset || !qh->vertex_mergeset) { +- qh_fprintf(qh, qh->ferr, 6388, "qhull internal error (qh_freemergesets): expecting mergesets. Got a NULL mergeset, qh.facet_mergeset (0x%x), qh.degen_mergeset (0x%x), qh.vertex_mergeset (0x%x)\n", ++ qh_fprintf(qh, qh->ferr, 6388, "qhull internal error (qh_freemergesets): expecting mergesets. Got a NULL mergeset, qh.facet_mergeset (%p), qh.degen_mergeset (%p), qh.vertex_mergeset (%p)\n", + qh->facet_mergeset, qh->degen_mergeset, qh->vertex_mergeset); + qh_errexit(qh, qh_ERRqhull, NULL, NULL); + } +@@ -2034,7 +2033,7 @@ ridgeT *qh_hashridge_find(qhT *qh, setT *hashtable, int hashsize, ridgeT *ridge, + void qh_initmergesets(qhT *qh /* qh.facet_mergeset,degen_mergeset,vertex_mergeset */) { + + if (qh->facet_mergeset || qh->degen_mergeset || qh->vertex_mergeset) { +- qh_fprintf(qh, qh->ferr, 6386, "qhull internal error (qh_initmergesets): expecting NULL mergesets. Got qh.facet_mergeset (0x%x), qh.degen_mergeset (0x%x), qh.vertex_mergeset (0x%x)\n", ++ qh_fprintf(qh, qh->ferr, 6386, "qhull internal error (qh_initmergesets): expecting NULL mergesets. Got qh.facet_mergeset (%p), qh.degen_mergeset (%p), qh.vertex_mergeset (%p)\n", + qh->facet_mergeset, qh->degen_mergeset, qh->vertex_mergeset); + qh_errexit(qh, qh_ERRqhull, NULL, NULL); + } +@@ -2304,7 +2303,7 @@ void qh_maybe_duplicateridge(qhT *qh, ridgeT *ridgeA) { + if (k == last) { + vertex= qh_findbest_ridgevertex(qh, ridge, &pinched, &dist); + trace2((qh, qh->ferr, 2069, "qh_maybe_duplicateridge: will merge v%d into v%d (dist %2.2g) due to duplicate ridges r%d/r%d with the same vertices. mergevertex set\n", +- pinched->id, vertex->id, dist, ridgeA->id, ridge->id, ridgeA->top->id, ridgeA->bottom->id, ridge->top->id, ridge->bottom->id)); ++ pinched->id, vertex->id, dist, ridgeA->id, ridge->id)); + qh_appendvertexmerge(qh, pinched, vertex, MRGvertices, dist, ridgeA, ridge); + ridge->mergevertex= True; /* disables check for duplicate vertices in qh_checkfacet */ + ridgeA->mergevertex= True; +diff --git a/src/libqhull_r/poly2_r.c b/src/libqhull_r/poly2_r.c +index 01758340..4d9a0c51 100644 +--- a/src/libqhull_r/poly2_r.c ++++ b/src/libqhull_r/poly2_r.c +@@ -1145,7 +1145,7 @@ boolT qh_checklists(qhT *qh, facetT *facetlist) { + vertex->visitid= qh->vertex_visit; + if (vertex->previous != previousvertex) { + qh_fprintf(qh, qh->ferr, 6427, "qhull internal error (qh_checklists): expecting v%d.previous == v%d. Got v%d\n", +- vertex->id, previousvertex, getid_(vertex->previous)); ++ vertex->id, previousvertex->id, getid_(vertex->previous)); + waserror= True; + errorvertex= vertex; + } +@@ -2280,7 +2280,8 @@ void qh_initialhull(qhT *qh, setT *vertices) { + zzinc_(Zdistcheck); + qh_distplane(qh, qh->interior_point, facet, &dist); /* duplicates qh_setfacetplane */ + if (dist > qh->DISTround) { /* clearly flipped, due to axis-parallel facet or coplanar firstfacet */ +- trace1((qh, qh->ferr, 1031, "qh_initialhull: initial orientation incorrect, qh.interior_point is %2.2g from f%d. Either axis-parallel facet or coplanar firstfacet f%d. Force outside orientation of all facets\n")); ++ trace1((qh, qh->ferr, 1031, "qh_initialhull: initial orientation incorrect, qh.interior_point is %2.2g from f%d. Either axis-parallel facet or coplanar firstfacet f%d. Force outside orientation of all facets\n", ++ dist, facet->id, firstfacet->id)); + FORALLfacets { /* reuse facet, then 'break' */ + facet->flipped= False; + facet->toporient ^= (unsigned char)True; +diff --git a/src/libqhullcpp/Qhull.cpp b/src/libqhullcpp/Qhull.cpp +index d5c75e92..3123e8ae 100644 +--- a/src/libqhullcpp/Qhull.cpp ++++ b/src/libqhullcpp/Qhull.cpp +@@ -357,7 +357,7 @@ initializeFeasiblePoint(int hulldim) + qh_errexit(qh_qh, qh_ERRmem, NULL, NULL); + } + if(feasible_point.size()!=static_cast(hulldim)){ +- qh_fprintf(qh_qh, qh_qh->ferr, 6210, "qhull error: dimension of feasiblePoint should be %d. It is %u\n", hulldim, feasible_point.size()); ++ qh_fprintf(qh_qh, qh_qh->ferr, 6210, "qhull error: dimension of feasiblePoint should be %d. It is %u\n", hulldim, (unsigned int)feasible_point.size()); + qh_errexit(qh_qh, qh_ERRmem, NULL, NULL); + } + qh_qh->feasible_point= static_cast(qh_malloc(static_cast(hulldim) * sizeof(coordT))); +diff --git a/src/testqset_r/testqset_r.c b/src/testqset_r/testqset_r.c +index b0253e0e..5ea87394 100644 +--- a/src/testqset_r/testqset_r.c ++++ b/src/testqset_r/testqset_r.c +@@ -532,7 +532,7 @@ void testSetequalInEtc(qhT *qh, int numInts, int *intarray, int checkEvery) + } + if(j>0){ + if(qh_setequal(ints, ints2)){ +- qh_fprintf(qh, stderr, 6324, "testqset_r (testSetequalInEtc): non-empty set equal to empty set\n", j); ++ qh_fprintf(qh, stderr, 6324, "testqset_r (testSetequalInEtc): non-empty set equal to empty set at %d\n", j); + error_count++; + } + qh_setfree(qh, &ints3); +@@ -551,23 +551,23 @@ void testSetequalInEtc(qhT *qh, int numInts, int *intarray, int checkEvery) + error_count++; + } + if(!qh_setequal_except(ints, intarray+j/2, ints3, intarray+j/2+1)){ +- qh_fprintf(qh, stderr, 6326, "testqset_r (qh_setequal_except): modified set not equal to original set except modified\n", j); ++ qh_fprintf(qh, stderr, 6326, "testqset_r (qh_setequal_except): modified set not equal to original set except modified at %d\n", j); + error_count++; + } + if(qh_setequal_except(ints, intarray+j/2, ints3, intarray)){ +- qh_fprintf(qh, stderr, 6327, "testqset_r (qh_setequal_except): modified set equal to original set with wrong excepts\n", j); ++ qh_fprintf(qh, stderr, 6327, "testqset_r (qh_setequal_except): modified set equal to original set with wrong excepts at %d\n", j); + error_count++; + } + if(!qh_setequal_skip(ints, j/2, ints3, j/2)){ +- qh_fprintf(qh, stderr, 6328, "testqset_r (qh_setequal_skip): modified set not equal to original set except modified\n", j); ++ qh_fprintf(qh, stderr, 6328, "testqset_r (qh_setequal_skip): modified set not equal to original set except modified at %d\n", j); + error_count++; + } + if(j>2 && qh_setequal_skip(ints, j/2, ints3, 0)){ +- qh_fprintf(qh, stderr, 6329, "testqset_r (qh_setequal_skip): modified set equal to original set with wrong excepts\n", j); ++ qh_fprintf(qh, stderr, 6329, "testqset_r (qh_setequal_skip): modified set equal to original set with wrong excepts at %d\n", j); + error_count++; + } + if(intarray+j/2+1!=qh_setdel(ints3, intarray+j/2+1)){ +- qh_fprintf(qh, stderr, 6330, "testqset_r (qh_setdel): failed to find added element\n", j); ++ qh_fprintf(qh, stderr, 6330, "testqset_r (qh_setdel): failed to find added element at %d\n", j); + error_count++; + } + checkSetContents(qh, "qh_setdel", ints3, j-1, 0, j-1, (j==1 ? -1 : j/2+1)); /* swaps last element with deleted element */ +@@ -786,7 +786,7 @@ void checkSetContents(qhT *qh, const char *name, setT *set, int count, int range + if(set){ + SETreturnsize_(set, actualSize); /* normally used only when speed is critical */ + if(*qh_setendpointer(set)!=NULL){ +- qh_fprintf(qh, stderr, 6344, "testqset_r (%s): qh_setendpointer(set), 0x%x, is not NULL terminator of set 0x%x\n", name, qh_setendpointer(set), set); ++ qh_fprintf(qh, stderr, 6344, "testqset_r (%s): qh_setendpointer(set), %p, is not NULL terminator of set %p\n", name, qh_setendpointer(set), set); + error_count++; + } + } + +From f7c3bbdfd23c034f5af66fa1f067691aac3378d8 Mon Sep 17 00:00:00 2001 +From: Elliott Sales de Andrade +Date: Thu, 28 Mar 2024 05:11:33 -0400 +Subject: [PATCH 3/3] Don't pass user-defined input as format string + +--- + src/libqhull/io.c | 2 +- + src/libqhull_r/io_r.c | 2 +- + 2 files changed, 2 insertions(+), 2 deletions(-) + +diff --git a/src/libqhull/io.c b/src/libqhull/io.c +index beed156a..7b7f4546 100644 +--- a/src/libqhull/io.c ++++ b/src/libqhull/io.c +@@ -1618,7 +1618,7 @@ void qh_printcenter(FILE *fp, qh_PRINT format, const char *string, facetT *facet + if (qh CENTERtype != qh_ASvoronoi && qh CENTERtype != qh_AScentrum) + return; + if (string) +- qh_fprintf(fp, 9066, string); ++ qh_fprintf(fp, 9066, "%s", string); + if (qh CENTERtype == qh_ASvoronoi) { + num= qh hull_dim-1; + if (!facet->normal || !facet->upperdelaunay || !qh ATinfinity) { +diff --git a/src/libqhull_r/io_r.c b/src/libqhull_r/io_r.c +index a80a5b14..389b1aa6 100644 +--- a/src/libqhull_r/io_r.c ++++ b/src/libqhull_r/io_r.c +@@ -1618,7 +1618,7 @@ void qh_printcenter(qhT *qh, FILE *fp, qh_PRINT format, const char *string, face + if (qh->CENTERtype != qh_ASvoronoi && qh->CENTERtype != qh_AScentrum) + return; + if (string) +- qh_fprintf(qh, fp, 9066, string); ++ qh_fprintf(qh, fp, 9066, "%s", string); + if (qh->CENTERtype == qh_ASvoronoi) { + num= qh->hull_dim-1; + if (!facet->normal || !facet->upperdelaunay || !qh->ATinfinity) { diff --git a/subprojects/qhull.wrap b/subprojects/qhull.wrap index c52b07d790cd..41cd88692d8b 100644 --- a/subprojects/qhull.wrap +++ b/subprojects/qhull.wrap @@ -7,3 +7,5 @@ source_filename = qhull-8.0.2.tgz source_hash = 8774e9a12c70b0180b95d6b0b563c5aa4bea8d5960c15e18ae3b6d2521d64f8b patch_directory = qhull-8.0.2 + +diff_files = qhull-143.patch diff --git a/tools/autoclose_prs.py b/tools/autoclose_prs.py new file mode 100644 index 000000000000..e2c08fbbc586 --- /dev/null +++ b/tools/autoclose_prs.py @@ -0,0 +1,56 @@ +"""Close PRs labeled with 'status: autoclose candidate' more than 14 days ago. + +Called from .github/workflows/autoclose_schedule.yml. + +Based on the scikit-learn script at https://github.com/scikit-learn/scikit-learn/blob/main/build_tools/github/autoclose_prs.py +""" + +import os +from datetime import datetime, timezone + +from github import Github + +CUTOFF_DAYS = 14 + + +def get_labeled_last_time(pr, label): + labeled_time = None + for event in pr.get_events(): + if event.event == "labeled" and event.label.name == label: + labeled_time = event.created_at + + return labeled_time + + +gh_repo = "matplotlib/matplotlib" +github_token = os.getenv("GITHUB_TOKEN") + +gh = Github(github_token) +repo = gh.get_repo(gh_repo) + + +now = datetime.now(timezone.utc) +label = "status: autoclose candidate" +prs = [ + each + for each in repo.get_issues(labels=[label]) + if each.pull_request is not None + and (now - get_labeled_last_time(each, label)).days > CUTOFF_DAYS +] +pr_numbers = [pr.number for pr in prs] +print(f"Found {len(prs)} PRs to autoclose: {pr_numbers}") + +message = ( + "Thank you for your interest in contributing to Matplotlib, but we cannot " + "accept your contribution as this pull request does not meet our development " + "standards.\n\n" + "Following our review policy, we are closing this PR after allowing two " + "weeks time for improvements.\n\n" + "Thank you for your understanding. If you think your PR has been closed " + "by mistake, please comment below." +) + +for pr in prs: + print(f"Closing PR #{pr.number} with comment") + pr.create_comment(message) + pr.edit(state="closed") diff --git a/tools/boilerplate.py b/tools/boilerplate.py index 962ae899c458..0a1a26c7cb76 100644 --- a/tools/boilerplate.py +++ b/tools/boilerplate.py @@ -1,12 +1,19 @@ """ Script to autogenerate pyplot wrappers. -When this script is run, the current contents of pyplot are -split into generatable and non-generatable content (via the magic header -:attr:`PYPLOT_MAGIC_HEADER`) and the generatable content is overwritten. -Hence, the non-generatable content should be edited in the pyplot.py file -itself, whereas the generatable content must be edited via templates in -this file. +pyplot.py consists of two parts: a hand-written part at the top, and an +automatically generated part at the bottom, starting with the comment + + ### REMAINING CONTENT GENERATED BY boilerplate.py ### + +This script generates the automatically generated part of pyplot.py. It +consists of colormap setter functions and wrapper functions for methods +of Figure and Axes. Whenever the API of one of the wrapped methods changes, +this script has to be rerun to keep pyplot.py up to date. + +The test ``lib/matplotlib/test_pyplot.py::test_pyplot_up_to_date`` checks +that the autogenerated part of pyplot.py is up to date. It will fail in the +case of an API mismatch and remind the developer to rerun this script. """ # Although it is possible to dynamically generate the pyplot functions at @@ -238,6 +245,7 @@ def boilerplate_gen(): 'fill_between', 'fill_betweenx', 'grid', + 'grouped_bar', 'hexbin', 'hist', 'stairs', @@ -255,8 +263,8 @@ def boilerplate_gen(): 'pcolormesh', 'phase_spectrum', 'pie', + 'pie_label', 'plot', - 'plot_date', 'psd', 'quiver', 'quiverkey', @@ -460,5 +468,13 @@ def update_sig_from_node(node, sig): if len(sys.argv) > 1: pyplot_path = Path(sys.argv[1]) else: - pyplot_path = Path(__file__).parent / "../lib/matplotlib/pyplot.py" + mpl_path = (Path(__file__).parent / ".." /"lib"/"matplotlib").resolve() + pyplot_path = mpl_path / "pyplot.py" + for cls in [Axes, Figure]: + if mpl_path not in Path(inspect.getfile(cls)).parents: + raise RuntimeError( + f"{cls.__name__} import path is not {mpl_path}.\n" + "Please make sure your Matplotlib installation " + "is from the same source checkout as boilerplate.py" + ) build_pyplot(pyplot_path) diff --git a/tools/cache_zenodo_svg.py b/tools/cache_zenodo_svg.py index 40814d21573c..07b67a3e04ee 100644 --- a/tools/cache_zenodo_svg.py +++ b/tools/cache_zenodo_svg.py @@ -63,6 +63,14 @@ def _get_xdg_cache_dir(): if __name__ == "__main__": data = { + "v3.10.7": "17298696", + "v3.10.6": "16999430", + "v3.10.5": "16644850", + "v3.10.3": "15375714", + "v3.10.1": "14940554", + "v3.10.0": "14464227", + "v3.9.4": "14436121", + "v3.9.3": "14249941", "v3.9.2": "13308876", "v3.9.1": "12652732", "v3.9.0": "11201097", diff --git a/tools/make_icons.py b/tools/make_icons.py index f09d40e92256..b253c0517c43 100755 --- a/tools/make_icons.py +++ b/tools/make_icons.py @@ -64,7 +64,7 @@ def make_icon(font_path, ccode): def make_matplotlib_icon(): fig = plt.figure(figsize=(1, 1)) fig.patch.set_alpha(0.0) - ax = fig.add_axes([0.025, 0.025, 0.95, 0.95], projection='polar') + ax = fig.add_axes((0.025, 0.025, 0.95, 0.95), projection='polar') ax.set_axisbelow(True) N = 7 diff --git a/tools/stubtest.py b/tools/stubtest.py index b79ab2f40dd0..d73d966de19e 100644 --- a/tools/stubtest.py +++ b/tools/stubtest.py @@ -108,6 +108,7 @@ def visit_ClassDef(self, node): [ "stubtest", "--mypy-config-file=pyproject.toml", + "--ignore-disjoint-bases", "--allowlist=ci/mypy-stubtest-allowlist.txt", f"--allowlist={p}", "matplotlib", diff --git a/tools/triage_tests.py b/tools/triage_tests.py index 5153b1c712cb..6df720f29d2b 100644 --- a/tools/triage_tests.py +++ b/tools/triage_tests.py @@ -263,7 +263,7 @@ def __init__(self, path, root, source): ] self.thumbnails = [self.dir / x for x in self.thumbnails] - if not Path(self.destdir, self.generated).exists(): + if self.destdir is None or not Path(self.destdir, self.generated).exists(): # This case arises from a check_figures_equal test. self.status = 'autogen' elif ((self.dir / self.generated).read_bytes() @@ -281,7 +281,6 @@ def get_dest_dir(self, reldir): path = self.source / baseline_dir / reldir if path.is_dir(): return path - raise ValueError(f"Can't find baseline dir for {reldir}") @property def display(self): diff --git a/tools/visualize_tests.py b/tools/visualize_tests.py index 9cff44cd72d1..040c1a1787a6 100644 --- a/tools/visualize_tests.py +++ b/tools/visualize_tests.py @@ -1,6 +1,6 @@ #!/usr/bin/env python # -# This builds a html page of all images from the image comparison tests +# This builds an html page of all images from the image comparison tests # and opens that page in the browser. # # $ python tools/visualize_tests.py diff --git a/tox.ini b/tox.ini index 3e19b48b6ba7..1d527a19ff52 100644 --- a/tox.ini +++ b/tox.ini @@ -4,7 +4,7 @@ # and then run "tox" from this directory. [tox] -envlist = py310, py311, py312, stubtest +envlist = py311, py312, py313, stubtest [testenv] changedir = /tmp