diff --git a/.github/workflows/cibuildwheel.yml b/.github/workflows/cibuildwheel.yml index 46a7c68f062e..fd8dc309a61c 100644 --- a/.github/workflows/cibuildwheel.yml +++ b/.github/workflows/cibuildwheel.yml @@ -41,12 +41,12 @@ jobs: SDIST_NAME: ${{ steps.sdist.outputs.SDIST_NAME }} steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: fetch-depth: 0 persist-credentials: false - - uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0 + - uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0 name: Install Python with: python-version: '3.11' @@ -72,7 +72,7 @@ jobs: run: twine check dist/* - name: Upload sdist result - uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 with: name: cibw-sdist path: dist/*.tar.gz @@ -137,13 +137,13 @@ jobs: steps: - name: Download sdist - uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0 + uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 with: name: cibw-sdist path: dist/ - name: Build wheels for CPython 3.14 - uses: pypa/cibuildwheel@9c00cb4f6b517705a3794b22395aedc36257242c # v3.2.1 + uses: pypa/cibuildwheel@298ed2fb2c105540f5ed055e8a6ad78d82dd3a7e # v3.3.1 with: package-dir: dist/${{ needs.build_sdist.outputs.SDIST_NAME }} env: @@ -153,7 +153,7 @@ jobs: CIBW_MANYLINUX_X86_64_IMAGE: manylinux_2_28 - name: Build wheels for CPython 3.13 - uses: pypa/cibuildwheel@9c00cb4f6b517705a3794b22395aedc36257242c # v3.2.1 + uses: pypa/cibuildwheel@298ed2fb2c105540f5ed055e8a6ad78d82dd3a7e # v3.3.1 with: package-dir: dist/${{ needs.build_sdist.outputs.SDIST_NAME }} env: @@ -162,7 +162,7 @@ jobs: CIBW_ARCHS: ${{ matrix.cibw_archs }} - name: Build wheels for CPython 3.12 - uses: pypa/cibuildwheel@9c00cb4f6b517705a3794b22395aedc36257242c # v3.2.1 + uses: pypa/cibuildwheel@298ed2fb2c105540f5ed055e8a6ad78d82dd3a7e # v3.3.1 with: package-dir: dist/${{ needs.build_sdist.outputs.SDIST_NAME }} env: @@ -170,7 +170,7 @@ jobs: CIBW_ARCHS: ${{ matrix.cibw_archs }} - name: Build wheels for CPython 3.11 - uses: pypa/cibuildwheel@9c00cb4f6b517705a3794b22395aedc36257242c # v3.2.1 + uses: pypa/cibuildwheel@298ed2fb2c105540f5ed055e8a6ad78d82dd3a7e # v3.3.1 with: package-dir: dist/${{ needs.build_sdist.outputs.SDIST_NAME }} env: @@ -178,7 +178,7 @@ jobs: CIBW_ARCHS: ${{ matrix.cibw_archs }} - name: Build wheels for PyPy - uses: pypa/cibuildwheel@9c00cb4f6b517705a3794b22395aedc36257242c # v3.2.1 + uses: pypa/cibuildwheel@298ed2fb2c105540f5ed055e8a6ad78d82dd3a7e # v3.3.1 with: package-dir: dist/${{ needs.build_sdist.outputs.SDIST_NAME }} env: @@ -187,7 +187,7 @@ jobs: CIBW_ENABLE: pypy if: matrix.cibw_archs != 'aarch64' && matrix.os != 'windows-latest' && matrix.os != 'windows-11-arm' - - uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 + - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 with: name: cibw-wheels-${{ runner.os }}-${{ matrix.cibw_archs }} path: ./wheelhouse/*.whl diff --git a/.github/workflows/circleci.yml b/.github/workflows/circleci.yml index ab410b194754..05a30eb032cb 100644 --- a/.github/workflows/circleci.yml +++ b/.github/workflows/circleci.yml @@ -28,7 +28,7 @@ jobs: runs-on: ubuntu-latest name: Post warnings/errors as review steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: persist-credentials: false @@ -41,7 +41,7 @@ jobs: - name: Set up reviewdog if: "${{ steps.fetch-artifacts.outputs.count != 0 }}" - uses: reviewdog/action-setup@d8edfce3dd5e1ec6978745e801f9c50b5ef80252 # v1.4.0 + 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 fdfc446af15b..5107bb92fa1c 100644 --- a/.github/workflows/clean_pr.yml +++ b/.github/workflows/clean_pr.yml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: fetch-depth: '0' persist-credentials: false diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 72d84e46e077..2aa1ce9f911d 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -27,12 +27,12 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: persist-credentials: false - name: Initialize CodeQL - uses: github/codeql-action/init@4e94bd11f71e507f7f87df81788dff88d1dacbfb # v4.31.0 + uses: github/codeql-action/init@5d4e8d1aca955e8d8589aabd499c5cae939e33c7 # v4.31.9 with: languages: ${{ matrix.language }} @@ -43,4 +43,4 @@ jobs: pip install --user -v . - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@4e94bd11f71e507f7f87df81788dff88d1dacbfb # v4.31.0 + uses: github/codeql-action/analyze@5d4e8d1aca955e8d8589aabd499c5cae939e33c7 # v4.31.9 diff --git a/.github/workflows/cygwin.yml b/.github/workflows/cygwin.yml index ba1fd6f57790..ae80020e08b7 100644 --- a/.github/workflows/cygwin.yml +++ b/.github/workflows/cygwin.yml @@ -79,7 +79,7 @@ jobs: - name: Fix line endings run: git config --global core.autocrlf input - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: fetch-depth: 0 persist-credentials: false @@ -140,21 +140,21 @@ jobs: # FreeType build fails with bash, succeeds with dash - name: Cache pip - uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 + uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1 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@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 + uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1 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@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 + uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1 with: path: | C:\cygwin\home\runneradmin\.cache\matplotlib diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml index f1c6d21019e3..bcc7d406043a 100644 --- a/.github/workflows/linting.yml +++ b/.github/workflows/linting.yml @@ -10,11 +10,11 @@ jobs: name: precommit runs-on: ubuntu-latest steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: fetch-depth: 0 persist-credentials: false - - uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0 + - uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0 with: python-version: "3.x" - uses: pre-commit/action@2c7b3805fd2a0fd8c1884dcaebf91fc102a13ecd # v3.0.1 @@ -27,12 +27,12 @@ jobs: permissions: checks: write steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: persist-credentials: false - name: Set up Python 3 - uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0 + uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0 with: python-version: '3.11' @@ -40,7 +40,7 @@ jobs: run: pip3 install ruff - name: Set up reviewdog - uses: reviewdog/action-setup@d8edfce3dd5e1ec6978745e801f9c50b5ef80252 # v1.4.0 + uses: reviewdog/action-setup@d8a7baabd7f3e8544ee4dbde3ee41d0011c3a93f # v1.5.0 - name: Run ruff env: @@ -56,12 +56,12 @@ jobs: permissions: checks: write steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: persist-credentials: false - name: Set up Python 3 - uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0 + uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0 with: python-version: '3.11' @@ -69,7 +69,7 @@ jobs: run: pip3 install -r requirements/testing/mypy.txt -r requirements/testing/all.txt - name: Set up reviewdog - uses: reviewdog/action-setup@d8edfce3dd5e1ec6978745e801f9c50b5ef80252 # v1.4.0 + uses: reviewdog/action-setup@d8a7baabd7f3e8544ee4dbde3ee41d0011c3a93f # v1.5.0 - name: Run mypy env: @@ -87,7 +87,7 @@ jobs: permissions: checks: write steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: persist-credentials: false diff --git a/.github/workflows/mypy-stubtest.yml b/.github/workflows/mypy-stubtest.yml index 3815efd08954..dad980b4fae9 100644 --- a/.github/workflows/mypy-stubtest.yml +++ b/.github/workflows/mypy-stubtest.yml @@ -12,17 +12,17 @@ jobs: permissions: checks: write steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: persist-credentials: false - name: Set up Python 3 - uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0 + uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0 with: python-version: '3.11' - name: Set up reviewdog - uses: reviewdog/action-setup@d8edfce3dd5e1ec6978745e801f9c50b5ef80252 # v1.4.0 + uses: reviewdog/action-setup@d8a7baabd7f3e8544ee4dbde3ee41d0011c3a93f # v1.5.0 - name: Install tox run: python -m pip install tox diff --git a/.github/workflows/nightlies.yml b/.github/workflows/nightlies.yml index 393ce2e73472..e8ff8ad7acbc 100644 --- a/.github/workflows/nightlies.yml +++ b/.github/workflows/nightlies.yml @@ -59,7 +59,7 @@ jobs: ls -l dist/ - name: Upload wheels to Anaconda Cloud as nightlies - uses: scientific-python/upload-nightly-action@b36e8c0c10dbcfd2e05bf95f17ef8c14fd708dbf # 0.6.2 + 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/stale-tidy.yml b/.github/workflows/stale-tidy.yml index 85c8fec38e63..1d0d42ce761b 100644 --- a/.github/workflows/stale-tidy.yml +++ b/.github/workflows/stale-tidy.yml @@ -9,7 +9,7 @@ jobs: if: github.repository == 'matplotlib/matplotlib' runs-on: ubuntu-latest steps: - - uses: actions/stale@5f858e3efba33a5ca4407a664cc011ad407f2008 # v10.1.0 + - uses: actions/stale@997185467fa4f803885201cee163a9f38240193d # v10.1.1 with: repo-token: ${{ secrets.GITHUB_TOKEN }} operations-per-run: 300 @@ -20,5 +20,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 bbc58eca4fb0..a61a0b2e92c8 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -2,14 +2,14 @@ name: 'Label inactive PRs' on: schedule: - - cron: '30 1 * * 1,3,5' + - cron: '30 1 * * 1' jobs: stale: if: github.repository == 'matplotlib/matplotlib' runs-on: ubuntu-latest steps: - - uses: actions/stale@5f858e3efba33a5ca4407a664cc011ad407f2008 # v10.1.0 + - uses: actions/stale@997185467fa4f803885201cee163a9f38240193d # v10.1.1 with: repo-token: ${{ secrets.GITHUB_TOKEN }} operations-per-run: 20 diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 048f11be14d2..28e2fcacb4ae 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -73,6 +73,8 @@ jobs: 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' - os: macos-14 # This runner is on M1 (arm64) chips. @@ -87,15 +89,19 @@ jobs: python-version: '3.13' # 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@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: fetch-depth: 0 persist-credentials: false - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0 + uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0 with: python-version: ${{ matrix.python-version }} allow-prereleases: true @@ -178,7 +184,7 @@ jobs: esac - name: Cache pip - uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 + uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1 if: startsWith(runner.os, 'Linux') with: path: ~/.cache/pip @@ -186,7 +192,7 @@ jobs: restore-keys: | ${{ matrix.os }}-py${{ matrix.python-version }}-pip- - name: Cache pip - uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 + uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1 if: startsWith(runner.os, 'macOS') with: path: ~/Library/Caches/pip @@ -194,7 +200,7 @@ jobs: restore-keys: | ${{ matrix.os }}-py${{ matrix.python-version }}-pip- - name: Cache ccache - uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 + uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1 with: path: | ~/.ccache @@ -202,7 +208,7 @@ jobs: restore-keys: | ${{ matrix.os }}-py${{ matrix.python-version }}-ccache- - name: Cache Matplotlib - uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 + uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1 with: path: | ~/.cache/matplotlib @@ -264,8 +270,8 @@ jobs: # 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 + 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' || @@ -389,12 +395,12 @@ jobs: fi - name: Upload code coverage if: ${{ !cancelled() && github.event_name != 'schedule' }} - uses: codecov/codecov-action@5a1091511ad55cbe89839c7260b706298ca349f7 # v5.5.1 + 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@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 + - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 if: failure() with: name: "${{ matrix.python-version }} ${{ matrix.os }} ${{ matrix.name-suffix }} result images" diff --git a/.gitignore b/.gitignore index 9389a1612b14..0460152a792f 100644 --- a/.gitignore +++ b/.gitignore @@ -104,6 +104,10 @@ __conda_version__.txt lib/png.lib lib/z.lib +# uv files # +############ +uv.lock + # Environments # ################ .env diff --git a/doc/_static/switcher.json b/doc/_static/switcher.json index 3912afd8a17d..36e743db21b8 100644 --- a/doc/_static/switcher.json +++ b/doc/_static/switcher.json @@ -1,7 +1,7 @@ [ { "name": "3.10 (stable)", - "version": "3.10.7", + "version": "3.10.8", "url": "https://matplotlib.org/stable/", "preferred": true }, diff --git a/doc/api/artist_api.rst b/doc/api/artist_api.rst index 0ca3fb364c41..f256d2b7164e 100644 --- a/doc/api/artist_api.rst +++ b/doc/api/artist_api.rst @@ -122,6 +122,7 @@ Figure and Axes Artist.set_figure Artist.get_figure + Artist.figure Children -------- diff --git a/doc/api/axes_api.rst b/doc/api/axes_api.rst index f5af8744a2bc..1868794dd921 100644 --- a/doc/api/axes_api.rst +++ b/doc/api/axes_api.rst @@ -628,6 +628,8 @@ Other Axes.get_transformed_clip_path_and_affine Axes.has_data Axes.set + Axes.get_figure + Axes.figure Axes.remove .. autoclass:: matplotlib.axes.Axes.ArtistList diff --git a/doc/api/matplotlib_configuration_api.rst b/doc/api/matplotlib_configuration_api.rst index d5dc60c80613..b2fafc08e5fc 100644 --- a/doc/api/matplotlib_configuration_api.rst +++ b/doc/api/matplotlib_configuration_api.rst @@ -24,8 +24,24 @@ Default values and styling ========================== .. py:data:: rcParams + :type: RcParams + + The global configuration settings for Matplotlib. + + This is a dictionary-like variable that stores the current configuration + settings. Many of the values control styling, but others control + various aspects of Matplotlib's behavior. + + See :doc:`/users/explain/configuration` for a full list of config + parameters. + + See :ref:`customizing` for usage information. + + Notes + ----- + This object is also available as ``plt.rcParams`` via the + `matplotlib.pyplot` module (which by convention is imported as ``plt``). - An instance of `RcParams` for handling default Matplotlib values. .. autoclass:: RcParams :no-members: diff --git a/doc/api/next_api_changes/behavior/28437-CH.rst b/doc/api/next_api_changes/behavior/28437-CH.rst index 6121dfec8163..bb303bbe9d3b 100644 --- a/doc/api/next_api_changes/behavior/28437-CH.rst +++ b/doc/api/next_api_changes/behavior/28437-CH.rst @@ -2,7 +2,7 @@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ When passing and array to ``imshow(..., alpha=...)``, the parameter was silently ignored -if the image data was a RGB or RBGA image or if :rc:`interpolation_state` +if the image data was an RGB or RBGA image or if :rc:`image.interpolation_stage` resolved to "rbga". This is now fixed, and the alpha array overwrites any previous transparency information. diff --git a/doc/api/next_api_changes/behavior/30824-AYS.rst b/doc/api/next_api_changes/behavior/30824-AYS.rst new file mode 100644 index 000000000000..a190bd537126 --- /dev/null +++ b/doc/api/next_api_changes/behavior/30824-AYS.rst @@ -0,0 +1,6 @@ +Bivariate colormaps now fully span the intended range of colors +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Bivariate colormaps generated by ``SegmentedBivarColormap`` (e.g., ``BiOrangeBlue``) +from a set of input colors now fully span that range of colors. There had been a bug +with the numerical interpolation such that the colormap did not actually include the +first or last colors. diff --git a/doc/api/next_api_changes/deprecations/30889-TH.rst b/doc/api/next_api_changes/deprecations/30889-TH.rst new file mode 100644 index 000000000000..d221ae30d4fb --- /dev/null +++ b/doc/api/next_api_changes/deprecations/30889-TH.rst @@ -0,0 +1,10 @@ +Transforms helper functions +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The following functions in the `.transforms` module are deprecated, +because they are considerer internal functionality and should not be used +by end users: + +- ``matplotlib.transforms.nonsingular`` +- ``matplotlib.transforms.interval_contains`` +- ``matplotlib.transforms.interval_contains_open`` diff --git a/doc/api/prev_api_changes/api_changes_3.10.1.rst b/doc/api/prev_api_changes/api_changes_3.10.1.rst index 26d43ddf8b17..71a2f71efc94 100644 --- a/doc/api/prev_api_changes/api_changes_3.10.1.rst +++ b/doc/api/prev_api_changes/api_changes_3.10.1.rst @@ -8,7 +8,7 @@ Behaviour ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ When passing and array to ``imshow(..., alpha=...)``, the parameter was silently ignored -if the image data was a RGB or RBGA image or if :rc:`interpolation_state` +if the image data was an RGB or RBGA image or if :rc:`image.interpolation_stage` resolved to "rbga". This is now fixed, and the alpha array overwrites any previous transparency information. diff --git a/doc/api/widgets_api.rst b/doc/api/widgets_api.rst index 014361e70377..739b0f8931e0 100644 --- a/doc/api/widgets_api.rst +++ b/doc/api/widgets_api.rst @@ -2,11 +2,53 @@ ``matplotlib.widgets`` ********************** -.. inheritance-diagram:: matplotlib.widgets +.. currentmodule:: matplotlib.widgets + +.. automodule:: matplotlib.widgets + :no-members: + :no-undoc-members: + + +Widget classes +============== + +.. inheritance-diagram:: matplotlib.widgets.Widget :parts: 1 + :private-bases: + :include-subclasses: +.. autosummary:: + :toctree: _as_gen + :template: autosummary.rst + :nosignatures: -.. automodule:: matplotlib.widgets - :members: - :undoc-members: - :show-inheritance: + Widget + AxesWidget + Cursor + MultiCursor + Button + CheckButtons + RadioButtons + SliderBase + Slider + RangeSlider + TextBox + _SelectorWidget + RectangleSelector + EllipseSelector + Lasso + LassoSelector + PolygonSelector + SpanSelector + SubplotTool + +Helper classes +============== + +.. autosummary:: + :toctree: _as_gen + :nosignatures: + + LockDraw + ToolHandles + ToolLineHandles diff --git a/doc/devel/contribute.rst b/doc/devel/contribute.rst index 3667d6e20a5d..bd4fe8e64c52 100644 --- a/doc/devel/contribute.rst +++ b/doc/devel/contribute.rst @@ -337,7 +337,7 @@ Start a pull request The preferred way to contribute to Matplotlib is to fork the `main repository `__ on GitHub, -then submit a "pull request" (PR). To work on a a pull request: +then submit a "pull request" (PR). To work on a pull request: #. **First** set up a development environment, either by cloning a copy of the Matplotlib repository to your own computer or by using Github codespaces, by diff --git a/doc/devel/license.rst b/doc/devel/license.rst index 7596f2f92348..ebdfd494f832 100644 --- a/doc/devel/license.rst +++ b/doc/devel/license.rst @@ -10,7 +10,7 @@ another project make sure it has a PSF, BSD, MIT or compatible license licenses). If it doesn't, you may consider contacting the author and asking them to relicense it. GPL and LGPL code are not acceptable in the main code base, though we are considering an alternative way of -distributing L/GPL code through an separate channel, possibly a +distributing L/GPL code through a separate channel, possibly a toolkit. If you include code, make sure you include a copy of that code's license in the license directory if the code's license requires you to distribute the license with it. Non-BSD compatible licenses diff --git a/doc/install/dependencies.rst b/doc/install/dependencies.rst index 11317669817f..d0567900ea4c 100644 --- a/doc/install/dependencies.rst +++ b/doc/install/dependencies.rst @@ -31,6 +31,16 @@ reference. * `Pillow `_ (>= 9.0) * `pyparsing `_ (>= 3) +.. note:: + + With **conda packages**, this set of minimal dependencies is realized in + the ``matplotlib-base`` conda package. Other packages in the conda + ecosystem that depend on Matplotlib should depend on ``matplotlib-base``. + + The ``matplotlib`` conda package additionally comes with ``pyside6`` + to have a working GUI backend out of the box for end users. This should + primarily be used to define end-user environments. See also the + `conda forge documentation `__. .. _optional_dependencies: diff --git a/doc/missing-references.json b/doc/missing-references.json index 1a3693c990e5..d27d2fa067ce 100644 --- a/doc/missing-references.json +++ b/doc/missing-references.json @@ -4,7 +4,7 @@ ":1" ], "matplotlib.axes._base._AxesBase": [ - "doc/api/artist_api.rst:202" + "doc/api/artist_api.rst:203" ], "matplotlib.backend_bases._Backend": [ "lib/matplotlib/backend_bases.py:docstring of matplotlib.backend_bases.ShowBase:1" @@ -18,7 +18,7 @@ "lib/matplotlib/backends/backend_tkcairo.py:docstring of matplotlib.backends.backend_tkcairo.FigureCanvasTkCairo:1" ], "matplotlib.image._ImageBase": [ - "doc/api/artist_api.rst:202", + "doc/api/artist_api.rst:203", "lib/matplotlib/image.py:docstring of matplotlib.image.AxesImage:1", "lib/matplotlib/image.py:docstring of matplotlib.image.BboxImage:1", "lib/matplotlib/image.py:docstring of matplotlib.image.FigureImage:1" @@ -67,7 +67,7 @@ "lib/matplotlib/projections/geo.py:docstring of matplotlib.projections.geo.MollweideAxes.MollweideTransform:1" ], "matplotlib.text._AnnotationBase": [ - "doc/api/artist_api.rst:202", + "doc/api/artist_api.rst:203", "lib/matplotlib/offsetbox.py:docstring of matplotlib.offsetbox.AnnotationBbox:1", "lib/matplotlib/text.py:docstring of matplotlib.text.Annotation:1" ], diff --git a/doc/release/github_stats.rst b/doc/release/github_stats.rst index 99ea3fa9c167..cd9e71ff9376 100644 --- a/doc/release/github_stats.rst +++ b/doc/release/github_stats.rst @@ -2,20 +2,19 @@ .. _github-stats: -GitHub statistics for 3.10.7 (Oct 08, 2025) +GitHub statistics for 3.10.8 (Nov 12, 2025) =========================================== -GitHub statistics for 2024/12/14 (tag: v3.10.0) - 2025/10/08 +GitHub statistics for 2024/12/14 (tag: v3.10.0) - 2025/11/12 These lists are automatically generated, and may be incomplete or contain duplicates. We closed 4 issues and merged 16 pull requests. -The full list can be seen `on GitHub `__ +The full list can be seen `on GitHub `__ -The following 32 authors contributed 422 commits. +The following 35 authors contributed 445 commits. * Aasma Gupta -* AASMA GUPTA * Antony Lee * Christine P. Chai * David Stansby @@ -24,6 +23,7 @@ The following 32 authors contributed 422 commits. * G.D. McBain * Greg Lucas * hannah +* heinrich5991 * hu-xiaonan * Ian Thomas * Inês Cachola @@ -31,19 +31,22 @@ The following 32 authors contributed 422 commits. * Jouni K. Seppänen * Khushi_29 * Kyle Sunden +* Lucas Gruwez * Lumberbot (aka Jack) * N R Navaneet * Nathan G. Wiseman +* Nathan Goldbaum +* Nick Coish * Oscar Gustafsson * Praful Gulani * Qian Zhang +* Rafael Katri * Raphael Erik Hviding * Roman * Ruth Comer * saikarna913 * Scott Shambaugh * Thomas A Caswell -* Tim Heap * Tim Hoffmann * Trygve Magnus Ræder @@ -51,29 +54,29 @@ GitHub issues and pull requests: Pull Requests (16): -* :ghpull:`30628`: Backport PR #30626 on branch v3.10.x (MNT: Fix new F401 unused imports warnings) -* :ghpull:`30626`: MNT: Fix new F401 unused imports warnings -* :ghpull:`30589`: Backport PR #29745: Use PEP8 style method and function names from -* :ghpull:`30614`: Backport PR #30612 on branch v3.10.x (MNT: update black pin) -* :ghpull:`30612`: MNT: update black pin -* :ghpull:`30572`: Backport PR #30571 on branch v3.10.x (CI: remove macos13) -* :ghpull:`30571`: CI: remove macos13 -* :ghpull:`30570`: Backport PR #30558 on branch v3.10.x (Fix stubtest with mypy 18) -* :ghpull:`30558`: Fix stubtest with mypy 18 -* :ghpull:`30540`: Backport PR #30539 on branch v3.10.x (Fix scale_unit/scale_units typo in quiver docs) -* :ghpull:`30539`: Fix scale_unit/scale_units typo in quiver docs -* :ghpull:`30518`: Backport PR #30497 on branch v3.10.x (TST: Use a temporary directory for test_save_figure_return) -* :ghpull:`30497`: TST: Use a temporary directory for test_save_figure_return -* :ghpull:`30506`: Backport PR #30490 on branch v3.10.x (Fix SVG rendering error in def update_background) -* :ghpull:`30490`: Fix SVG rendering error in def update_background -* :ghpull:`30494`: Backport PR #30492 on branch v3.10.x (DOC: pytz link should be from PyPI) +* :ghpull:`30717`: Backport PR #30714 on branch v3.10.x (FIX: Gracefully handle numpy arrays as input to check_in_list()) +* :ghpull:`30714`: FIX: Gracefully handle numpy arrays as input to check_in_list() +* :ghpull:`30560`: Consistent zoom boxes +* :ghpull:`30711`: Backport PR #30697 on branch v3.10.x (BUG: raise when creating a MacOS FigureManager outside the main thread) +* :ghpull:`30697`: BUG: raise when creating a MacOS FigureManager outside the main thread +* :ghpull:`30656`: Backport PR #29810 on branch v3.10.x (Declare free-threaded support in MacOS backend extension) +* :ghpull:`30702`: Backport PR #30624 on branch v3.10.x (TST: Increase tolerances for Ghostscript 10.06) +* :ghpull:`30700`: Backport PR #30698 on branch v3.10.x (BLD: update trove metadata to support py3.14) +* :ghpull:`30624`: TST: Increase tolerances for Ghostscript 10.06 +* :ghpull:`30698`: BLD: update trove metadata to support py3.14 +* :ghpull:`30688`: Backport PR #30687 on branch v3.10.x (DOC: Fix pip link) +* :ghpull:`30675`: Backport PR #30657 on branch v3.10.x (Fix AttributeError: module 'gi' has no attribute 'require_version') +* :ghpull:`30674`: Backport PR #30672 on branch v3.10.x (Use pathlib.Path instead of matplotlib.path.Path in text.pyi) +* :ghpull:`30672`: Use pathlib.Path instead of matplotlib.path.Path in text.pyi +* :ghpull:`30657`: Fix ``AttributeError: module 'gi' has no attribute 'require_version'`` +* :ghpull:`29810`: Declare free-threaded support in MacOS backend extension Issues (4): -* :ghissue:`30611`: [MNT]: black version -* :ghissue:`30551`: [Bug]: Mypy stubtest failure on disjoint_base -* :ghissue:`30493`: [Bug]: test_save_figure_return seems flaky -* :ghissue:`30485`: [Bug]: figures with SpanSelector(..., useblit=True) can't be saved to SVG or PDF +* :ghissue:`30706`: [Bug]: Axes.grouped_bar() with non-string orientation (e.g., NumPy array) raises ambiguous truth-value error instead of clean ValueError +* :ghissue:`30666`: [Bug]: calling pyplot.gca() outside the main thread crashes the interpreter with the MacOS backend +* :ghissue:`30669`: [Bug]: Type hint for fontproperties keyword in text.pyi is wrong +* :ghissue:`30654`: [Bug]: error plotting: AttributeError: module 'gi' has no attribute 'require_version' Previous GitHub statistics diff --git a/doc/release/next_whats_new/README.rst b/doc/release/next_whats_new/README.rst index a680f5120f52..1a7039a4a5db 100644 --- a/doc/release/next_whats_new/README.rst +++ b/doc/release/next_whats_new/README.rst @@ -34,7 +34,7 @@ Include contents of the form:: details should be left out when they do not impact usage, for example implementation details. - The description may include a a short instructive example, if it helps to + The description may include a short instructive example, if it helps to understand the feature. Please avoid using references in section titles, as it causes links to be diff --git a/doc/release/next_whats_new/hist_color.rst b/doc/release/next_whats_new/hist_color.rst new file mode 100644 index 000000000000..2d6a4adb3464 --- /dev/null +++ b/doc/release/next_whats_new/hist_color.rst @@ -0,0 +1,5 @@ +``hist()`` supports a single color for multiple datasets +-------------------------------------------------------- + +It is now possible to pass a single *color* value to `~.Axes.hist()`. This +value is applied to all datasets. diff --git a/doc/release/prev_whats_new/github_stats_3.10.7.rst b/doc/release/prev_whats_new/github_stats_3.10.7.rst new file mode 100644 index 000000000000..8c12e4050219 --- /dev/null +++ b/doc/release/prev_whats_new/github_stats_3.10.7.rst @@ -0,0 +1,74 @@ +.. _github-stats_3-10-7: + +GitHub statistics for 3.10.7 (Oct 08, 2025) +=========================================== + +GitHub statistics for 2024/12/14 (tag: v3.10.0) - 2025/10/08 + +These lists are automatically generated, and may be incomplete or contain duplicates. + +We closed 4 issues and merged 16 pull requests. +The full list can be seen `on GitHub `__ + +The following 32 authors contributed 422 commits. + +* Aasma Gupta +* AASMA GUPTA +* Antony Lee +* Christine P. Chai +* David Stansby +* dependabot[bot] +* Elliott Sales de Andrade +* G.D. McBain +* Greg Lucas +* hannah +* hu-xiaonan +* Ian Thomas +* Inês Cachola +* Jody Klymak +* Jouni K. Seppänen +* Khushi_29 +* Kyle Sunden +* Lumberbot (aka Jack) +* N R Navaneet +* Nathan G. Wiseman +* Oscar Gustafsson +* Praful Gulani +* Qian Zhang +* Raphael Erik Hviding +* Roman +* Ruth Comer +* saikarna913 +* Scott Shambaugh +* Thomas A Caswell +* Tim Heap +* Tim Hoffmann +* Trygve Magnus Ræder + +GitHub issues and pull requests: + +Pull Requests (16): + +* :ghpull:`30628`: Backport PR #30626 on branch v3.10.x (MNT: Fix new F401 unused imports warnings) +* :ghpull:`30626`: MNT: Fix new F401 unused imports warnings +* :ghpull:`30589`: Backport PR #29745: Use PEP8 style method and function names from +* :ghpull:`30614`: Backport PR #30612 on branch v3.10.x (MNT: update black pin) +* :ghpull:`30612`: MNT: update black pin +* :ghpull:`30572`: Backport PR #30571 on branch v3.10.x (CI: remove macos13) +* :ghpull:`30571`: CI: remove macos13 +* :ghpull:`30570`: Backport PR #30558 on branch v3.10.x (Fix stubtest with mypy 18) +* :ghpull:`30558`: Fix stubtest with mypy 18 +* :ghpull:`30540`: Backport PR #30539 on branch v3.10.x (Fix scale_unit/scale_units typo in quiver docs) +* :ghpull:`30539`: Fix scale_unit/scale_units typo in quiver docs +* :ghpull:`30518`: Backport PR #30497 on branch v3.10.x (TST: Use a temporary directory for test_save_figure_return) +* :ghpull:`30497`: TST: Use a temporary directory for test_save_figure_return +* :ghpull:`30506`: Backport PR #30490 on branch v3.10.x (Fix SVG rendering error in def update_background) +* :ghpull:`30490`: Fix SVG rendering error in def update_background +* :ghpull:`30494`: Backport PR #30492 on branch v3.10.x (DOC: pytz link should be from PyPI) + +Issues (4): + +* :ghissue:`30611`: [MNT]: black version +* :ghissue:`30551`: [Bug]: Mypy stubtest failure on disjoint_base +* :ghissue:`30493`: [Bug]: test_save_figure_return seems flaky +* :ghissue:`30485`: [Bug]: figures with SpanSelector(..., useblit=True) can't be saved to SVG or PDF diff --git a/doc/release/prev_whats_new/whats_new_0.98.4.rst b/doc/release/prev_whats_new/whats_new_0.98.4.rst index fb08b8355971..8091c1277a5d 100644 --- a/doc/release/prev_whats_new/whats_new_0.98.4.rst +++ b/doc/release/prev_whats_new/whats_new_0.98.4.rst @@ -138,7 +138,7 @@ psd amplitude scaling Ryan May did a lot of work to rationalize the amplitude scaling of :func:`~matplotlib.pyplot.psd` and friends. See -:doc:`/gallery/lines_bars_and_markers/psd_demo`. +:doc:`/gallery/statistics/psd_demo`. The changes should increase MATLAB compatibility and increase scaling options. diff --git a/doc/release/release_notes.rst b/doc/release/release_notes.rst index b8bd756ce69b..8ea4ad3d4f57 100644 --- a/doc/release/release_notes.rst +++ b/doc/release/release_notes.rst @@ -23,6 +23,7 @@ Version 3.10 ../api/prev_api_changes/api_changes_3.10.1.rst ../api/prev_api_changes/api_changes_3.10.0.rst github_stats.rst + prev_whats_new/github_stats_3.10.7.rst prev_whats_new/github_stats_3.10.6.rst prev_whats_new/github_stats_3.10.5.rst prev_whats_new/github_stats_3.10.3.rst diff --git a/doc/users/_rcparams_generated.rst b/doc/users/_rcparams_generated.rst new file mode 100644 index 000000000000..19f972771ea9 --- /dev/null +++ b/doc/users/_rcparams_generated.rst @@ -0,0 +1,1674 @@ +.. + autogenerated rcParams documentation. Update via + > python -c "from matplotlib import rcsetup; rcsetup._write_rcParam_rst()" + + +.. _rcparam_webagg_port: + +webagg.port: ``8988`` + The port to use for the web server in the WebAgg backend. + +.. _rcparam_webagg_address: + +webagg.address: ``'127.0.0.1'`` + The address on which the WebAgg web server should be reachable. + +.. _rcparam_webagg_port_retries: + +webagg.port_retries: ``50`` + If webagg.port is unavailable, a number of other random ports will be tried until one that is available is found. + +.. _rcparam_webagg_open_in_browser: + +webagg.open_in_browser: ``True`` + When True, open the web browser to the plot that is shown + +.. _rcparam_backend_fallback: + +backend_fallback: ``True`` + If you are running pyplot inside a GUI and your backend choice conflicts, we will automatically try to find a compatible one for you if backend_fallback is True + +.. _rcparam_interactive: + +interactive: ``False`` + *no description* + +.. _rcparam_figure_hooks: + +figure.hooks: ``[]`` + list of dotted.module.name:dotted.callable.name + +.. _rcparam_toolbar: + +toolbar: ``'toolbar2'`` + {None, toolbar2, toolmanager} + +.. _rcparam_timezone: + +timezone: ``'UTC'`` + a pytz timezone string, e.g., US/Central or Europe/Paris + +.. _rcparam_lines_linewidth: + +lines.linewidth: ``1.5`` + line width in points + +.. _rcparam_lines_linestyle: + +lines.linestyle: ``'-'`` + solid line + +.. _rcparam_lines_color: + +lines.color: ``'C0'`` + has no affect on plot(); see axes.prop_cycle + +.. _rcparam_lines_marker: + +lines.marker: ``'None'`` + the default marker + +.. _rcparam_lines_markerfacecolor: + +lines.markerfacecolor: ``'auto'`` + the default marker face color + +.. _rcparam_lines_markeredgecolor: + +lines.markeredgecolor: ``'auto'`` + the default marker edge color + +.. _rcparam_lines_markeredgewidth: + +lines.markeredgewidth: ``1.0`` + the line width around the marker symbol + +.. _rcparam_lines_markersize: + +lines.markersize: ``6.0`` + marker size, in points + +.. _rcparam_lines_dash_joinstyle: + +lines.dash_joinstyle: ``'round'`` + {miter, round, bevel} + +.. _rcparam_lines_dash_capstyle: + +lines.dash_capstyle: ``'butt'`` + {butt, round, projecting} + +.. _rcparam_lines_solid_joinstyle: + +lines.solid_joinstyle: ``'round'`` + {miter, round, bevel} + +.. _rcparam_lines_solid_capstyle: + +lines.solid_capstyle: ``'projecting'`` + {butt, round, projecting} + +.. _rcparam_lines_antialiased: + +lines.antialiased: ``True`` + render lines in antialiased (no jaggies) + +.. _rcparam_lines_dashed_pattern: + +lines.dashed_pattern: ``[3.7, 1.6]`` + The dash pattern for linestyle 'dashed' + +.. _rcparam_lines_dashdot_pattern: + +lines.dashdot_pattern: ``[6.4, 1.6, 1.0, 1.6]`` + The dash pattern for linestyle 'dashdot' + +.. _rcparam_lines_dotted_pattern: + +lines.dotted_pattern: ``[1.0, 1.65]`` + The dash pattern for linestyle 'dotted' + +.. _rcparam_lines_scale_dashes: + +lines.scale_dashes: ``True`` + *no description* + +.. _rcparam_markers_fillstyle: + +markers.fillstyle: ``'full'`` + {full, left, right, bottom, top, none} + +.. _rcparam_pcolor_shading: + +pcolor.shading: ``'auto'`` + *no description* + +.. _rcparam_pcolormesh_snap: + +pcolormesh.snap: ``True`` + Whether to snap the mesh to pixel boundaries. This is provided solely to allow old test images to remain unchanged. Set to False to obtain the previous behavior. + +.. _rcparam_patch_linewidth: + +patch.linewidth: ``1.0`` + edge width in points. + +.. _rcparam_patch_facecolor: + +patch.facecolor: ``'C0'`` + *no description* + +.. _rcparam_patch_edgecolor: + +patch.edgecolor: ``'black'`` + By default, Patches and Collections do not draw edges. This value is only used if facecolor is "none" (an Artist without facecolor and edgecolor would be invisible) or if patch.force_edgecolor is True. + +.. _rcparam_patch_force_edgecolor: + +patch.force_edgecolor: ``False`` + By default, Patches and Collections do not draw edges. Set this to True to draw edges with patch.edgedcolor as the default edgecolor. This is mainly relevant for styles. + +.. _rcparam_patch_antialiased: + +patch.antialiased: ``True`` + render patches in antialiased (no jaggies) + +.. _rcparam_hatch_color: + +hatch.color: ``'edge'`` + *no description* + +.. _rcparam_hatch_linewidth: + +hatch.linewidth: ``1.0`` + *no description* + +.. _rcparam_boxplot_notch: + +boxplot.notch: ``False`` + *no description* + +.. _rcparam_boxplot_vertical: + +boxplot.vertical: ``True`` + *no description* + +.. _rcparam_boxplot_whiskers: + +boxplot.whiskers: ``1.5`` + *no description* + +.. _rcparam_boxplot_bootstrap: + +boxplot.bootstrap: ``None`` + *no description* + +.. _rcparam_boxplot_patchartist: + +boxplot.patchartist: ``False`` + *no description* + +.. _rcparam_boxplot_showmeans: + +boxplot.showmeans: ``False`` + *no description* + +.. _rcparam_boxplot_showcaps: + +boxplot.showcaps: ``True`` + *no description* + +.. _rcparam_boxplot_showbox: + +boxplot.showbox: ``True`` + *no description* + +.. _rcparam_boxplot_showfliers: + +boxplot.showfliers: ``True`` + *no description* + +.. _rcparam_boxplot_meanline: + +boxplot.meanline: ``False`` + *no description* + +.. _rcparam_boxplot_flierprops_color: + +boxplot.flierprops.color: ``'black'`` + *no description* + +.. _rcparam_boxplot_flierprops_marker: + +boxplot.flierprops.marker: ``'o'`` + *no description* + +.. _rcparam_boxplot_flierprops_markerfacecolor: + +boxplot.flierprops.markerfacecolor: ``'none'`` + *no description* + +.. _rcparam_boxplot_flierprops_markeredgecolor: + +boxplot.flierprops.markeredgecolor: ``'black'`` + *no description* + +.. _rcparam_boxplot_flierprops_markeredgewidth: + +boxplot.flierprops.markeredgewidth: ``1.0`` + *no description* + +.. _rcparam_boxplot_flierprops_markersize: + +boxplot.flierprops.markersize: ``6.0`` + *no description* + +.. _rcparam_boxplot_flierprops_linestyle: + +boxplot.flierprops.linestyle: ``'none'`` + *no description* + +.. _rcparam_boxplot_flierprops_linewidth: + +boxplot.flierprops.linewidth: ``1.0`` + *no description* + +.. _rcparam_boxplot_boxprops_color: + +boxplot.boxprops.color: ``'black'`` + *no description* + +.. _rcparam_boxplot_boxprops_linewidth: + +boxplot.boxprops.linewidth: ``1.0`` + *no description* + +.. _rcparam_boxplot_boxprops_linestyle: + +boxplot.boxprops.linestyle: ``'-'`` + *no description* + +.. _rcparam_boxplot_whiskerprops_color: + +boxplot.whiskerprops.color: ``'black'`` + *no description* + +.. _rcparam_boxplot_whiskerprops_linewidth: + +boxplot.whiskerprops.linewidth: ``1.0`` + *no description* + +.. _rcparam_boxplot_whiskerprops_linestyle: + +boxplot.whiskerprops.linestyle: ``'-'`` + *no description* + +.. _rcparam_boxplot_capprops_color: + +boxplot.capprops.color: ``'black'`` + *no description* + +.. _rcparam_boxplot_capprops_linewidth: + +boxplot.capprops.linewidth: ``1.0`` + *no description* + +.. _rcparam_boxplot_capprops_linestyle: + +boxplot.capprops.linestyle: ``'-'`` + *no description* + +.. _rcparam_boxplot_medianprops_color: + +boxplot.medianprops.color: ``'C1'`` + *no description* + +.. _rcparam_boxplot_medianprops_linewidth: + +boxplot.medianprops.linewidth: ``1.0`` + *no description* + +.. _rcparam_boxplot_medianprops_linestyle: + +boxplot.medianprops.linestyle: ``'-'`` + *no description* + +.. _rcparam_boxplot_meanprops_color: + +boxplot.meanprops.color: ``'C2'`` + *no description* + +.. _rcparam_boxplot_meanprops_marker: + +boxplot.meanprops.marker: ``'^'`` + *no description* + +.. _rcparam_boxplot_meanprops_markerfacecolor: + +boxplot.meanprops.markerfacecolor: ``'C2'`` + *no description* + +.. _rcparam_boxplot_meanprops_markeredgecolor: + +boxplot.meanprops.markeredgecolor: ``'C2'`` + *no description* + +.. _rcparam_boxplot_meanprops_markersize: + +boxplot.meanprops.markersize: ``6.0`` + *no description* + +.. _rcparam_boxplot_meanprops_linestyle: + +boxplot.meanprops.linestyle: ``'--'`` + *no description* + +.. _rcparam_boxplot_meanprops_linewidth: + +boxplot.meanprops.linewidth: ``1.0`` + *no description* + +.. _rcparam_font_family: + +font.family: ``['sans-serif']`` + *no description* + +.. _rcparam_font_style: + +font.style: ``'normal'`` + *no description* + +.. _rcparam_font_variant: + +font.variant: ``'normal'`` + *no description* + +.. _rcparam_font_weight: + +font.weight: ``'normal'`` + *no description* + +.. _rcparam_font_stretch: + +font.stretch: ``'normal'`` + *no description* + +.. _rcparam_font_size: + +font.size: ``10.0`` + *no description* + +.. _rcparam_font_serif: + +font.serif: ``['DejaVu Serif', 'Bitstream Vera Serif', 'Computer Modern Roman', 'New Century Schoolbook', 'Century Schoolbook L', 'Utopia', 'ITC Bookman', 'Bookman', 'Nimbus Roman No9 L', 'Times New Roman', 'Times', 'Palatino', 'Charter', 'serif']`` + *no description* + +.. _rcparam_font_sans-serif: + +font.sans-serif: ``['DejaVu Sans', 'Bitstream Vera Sans', 'Computer Modern Sans Serif', 'Lucida Grande', 'Verdana', 'Geneva', 'Lucid', 'Arial', 'Helvetica', 'Avant Garde', 'sans-serif']`` + *no description* + +.. _rcparam_font_cursive: + +font.cursive: ``['Apple Chancery', 'Textile', 'Zapf Chancery', 'Sand', 'Script MT', 'Felipa', 'Comic Neue', 'Comic Sans MS', 'cursive']`` + *no description* + +.. _rcparam_font_fantasy: + +font.fantasy: ``['Chicago', 'Charcoal', 'Impact', 'Western', 'xkcd script', 'fantasy']`` + *no description* + +.. _rcparam_font_monospace: + +font.monospace: ``['DejaVu Sans Mono', 'Bitstream Vera Sans Mono', 'Computer Modern Typewriter', 'Andale Mono', 'Nimbus Mono L', 'Courier New', 'Courier', 'Fixed', 'Terminal', 'monospace']`` + *no description* + +.. _rcparam_font_enable_last_resort: + +font.enable_last_resort: ``True`` + If True, then Unicode Consortium's Last Resort font will be appended to all font selections. This ensures that there will always be a glyph displayed. + +.. _rcparam_text_color: + +text.color: ``'black'`` + *no description* + +.. _rcparam_text_hinting: + +text.hinting: ``'force_autohint'`` + FreeType hinting flag ("foo" corresponds to FT_LOAD_FOO); may be one of the following (Proprietary Matplotlib-specific synonyms are given in parentheses, but their use is discouraged): - default: Use the font's native hinter if possible, else FreeType's auto-hinter. ("either" is a synonym).- no_autohint: Use the font's native hinter if possible, else don't hint. ("native" is a synonym.)- force_autohint: Use FreeType's auto-hinter. ("auto" is a synonym.)- no_hinting: Disable hinting. ("none" is a synonym.) + +.. _rcparam_text_hinting_factor: + +text.hinting_factor: ``8`` + Specifies the amount of softness for hinting in the horizontal direction. A value of 1 will hint to full pixels. A value of 2 will hint to half pixels etc. + +.. _rcparam_text_kerning_factor: + +text.kerning_factor: ``0`` + Specifies the scaling factor for kerning values. This is provided solely to allow old test images to remain unchanged. Set to 6 to obtain previous behavior. Values other than 0 or 6 have no defined meaning. + +.. _rcparam_text_antialiased: + +text.antialiased: ``True`` + If True (default), the text will be antialiased. This only affects raster outputs. + +.. _rcparam_text_parse_math: + +text.parse_math: ``True`` + Use mathtext if there is an even number of unescaped dollar signs. + +.. _rcparam_text_usetex: + +text.usetex: ``False`` + use latex for all text handling. The following fonts are supported through the usual rc parameter settings: new century schoolbook, bookman, times, palatino, zapf chancery, charter, serif, sans-serif, helvetica, avant garde, courier, monospace, computer modern roman, computer modern sans serif, computer modern typewriter + +.. _rcparam_text_latex_preamble: + +text.latex.preamble: ``''`` + IMPROPER USE OF THIS FEATURE WILL LEAD TO LATEX FAILURES AND IS THEREFORE UNSUPPORTED. PLEASE DO NOT ASK FOR HELP IF THIS FEATURE DOES NOT DO WHAT YOU EXPECT IT TO. text.latex.preamble is a single line of LaTeX code that will be passed on to the LaTeX system. It may contain any code that is valid for the LaTeX "preamble", i.e. between the "\documentclass" and "\begin{document}" statements. Note that it has to be put on a single line, which may become quite long. The following packages are always loaded with usetex, so beware of package collisions: color, fix-cm, geometry, graphicx, textcomp. PostScript (PSNFSS) font packages may also be loaded, depending on your font settings. + +.. _rcparam_mathtext_fontset: + +mathtext.fontset: ``'dejavusans'`` + Should be 'dejavusans' (default), 'dejavuserif', 'cm' (Computer Modern), 'stix', 'stixsans' or 'custom' + +.. _rcparam_mathtext_bf: + +mathtext.bf: ``'sans:bold'`` + *no description* + +.. _rcparam_mathtext_bfit: + +mathtext.bfit: ``'sans:italic:bold'`` + *no description* + +.. _rcparam_mathtext_cal: + +mathtext.cal: ``'cursive'`` + *no description* + +.. _rcparam_mathtext_it: + +mathtext.it: ``'sans:italic'`` + *no description* + +.. _rcparam_mathtext_rm: + +mathtext.rm: ``'sans'`` + *no description* + +.. _rcparam_mathtext_sf: + +mathtext.sf: ``'sans'`` + *no description* + +.. _rcparam_mathtext_tt: + +mathtext.tt: ``'monospace'`` + *no description* + +.. _rcparam_mathtext_fallback: + +mathtext.fallback: ``'cm'`` + Select fallback font from ['cm' (Computer Modern), 'stix', 'stixsans'] when a symbol cannot be found in one of the custom math fonts. Select 'None' to not perform fallback and replace the missing character by a dummy symbol. + +.. _rcparam_mathtext_default: + +mathtext.default: ``'it'`` + The default font to use for math. Can be any of the LaTeX font names, including the special name "regular" for the same font used in regular text. + +.. _rcparam_axes_facecolor: + +axes.facecolor: ``'white'`` + axes background color + +.. _rcparam_axes_edgecolor: + +axes.edgecolor: ``'black'`` + axes edge color + +.. _rcparam_axes_linewidth: + +axes.linewidth: ``0.8`` + edge line width + +.. _rcparam_axes_grid: + +axes.grid: ``False`` + display grid or not + +.. _rcparam_axes_grid_axis: + +axes.grid.axis: ``'both'`` + which axis the grid should apply to + +.. _rcparam_axes_grid_which: + +axes.grid.which: ``'major'`` + grid lines at {major, minor, both} ticks + +.. _rcparam_axes_titlelocation: + +axes.titlelocation: ``'center'`` + alignment of the title: {left, right, center} + +.. _rcparam_axes_titlesize: + +axes.titlesize: ``'large'`` + font size of the axes title + +.. _rcparam_axes_titleweight: + +axes.titleweight: ``'normal'`` + font weight of title + +.. _rcparam_axes_titlecolor: + +axes.titlecolor: ``'auto'`` + color of the axes title, auto falls back to text.color as default value + +.. _rcparam_axes_titley: + +axes.titley: ``None`` + position title (axes relative units). None implies auto + +.. _rcparam_axes_titlepad: + +axes.titlepad: ``6.0`` + pad between axes and title in points + +.. _rcparam_axes_labelsize: + +axes.labelsize: ``'medium'`` + font size of the x and y labels + +.. _rcparam_axes_labelpad: + +axes.labelpad: ``4.0`` + space between label and axis + +.. _rcparam_axes_labelweight: + +axes.labelweight: ``'normal'`` + weight of the x and y labels + +.. _rcparam_axes_labelcolor: + +axes.labelcolor: ``'black'`` + *no description* + +.. _rcparam_axes_axisbelow: + +axes.axisbelow: ``'line'`` + draw axis gridlines and ticks: - below patches (True) - above patches but below lines ('line') - above all (False) + +.. _rcparam_axes_formatter_limits: + +axes.formatter.limits: ``[-5, 6]`` + use scientific notation if log10 of the axis range is smaller than the first or larger than the second + +.. _rcparam_axes_formatter_use_locale: + +axes.formatter.use_locale: ``False`` + When True, format tick labels according to the user's locale. For example, use ',' as a decimal separator in the fr_FR locale. + +.. _rcparam_axes_formatter_use_mathtext: + +axes.formatter.use_mathtext: ``False`` + When True, use mathtext for scientific notation. + +.. _rcparam_axes_formatter_min_exponent: + +axes.formatter.min_exponent: ``0`` + minimum exponent to format in scientific notation + +.. _rcparam_axes_formatter_useoffset: + +axes.formatter.useoffset: ``True`` + If True, the tick label formatter will default to labeling ticks relative to an offset when the data range is small compared to the minimum absolute value of the data. + +.. _rcparam_axes_formatter_offset_threshold: + +axes.formatter.offset_threshold: ``4`` + When useoffset is True, the offset will be used when it can remove at least this number of significant digits from tick labels. + +.. _rcparam_axes_spines_left: + +axes.spines.left: ``True`` + display axis spines + +.. _rcparam_axes_spines_bottom: + +axes.spines.bottom: ``True`` + *no description* + +.. _rcparam_axes_spines_top: + +axes.spines.top: ``True`` + *no description* + +.. _rcparam_axes_spines_right: + +axes.spines.right: ``True`` + *no description* + +.. _rcparam_axes_unicode_minus: + +axes.unicode_minus: ``True`` + use Unicode for the minus symbol rather than hyphen. See https://en.wikipedia.org/wiki/Plus_and_minus_signs#Character_codes + +.. _rcparam_axes_prop_cycle: + +axes.prop_cycle: ``cycler('color', [(0.12156862745098039, 0.4666666666666667, 0.7058823529411765), (1.0, 0.4980392156862745, 0.054901960784313725), (0.17254901960784313, 0.6274509803921569, 0.17254901960784313), (0.8392156862745098, 0.15294117647058825, 0.1568627450980392), (0.5803921568627451, 0.403921568627451, 0.7411764705882353), (0.5490196078431373, 0.33725490196078434, 0.29411764705882354), (0.8901960784313725, 0.4666666666666667, 0.7607843137254902), (0.4980392156862745, 0.4980392156862745, 0.4980392156862745), (0.7372549019607844, 0.7411764705882353, 0.13333333333333333), (0.09019607843137255, 0.7450980392156863, 0.8117647058823529)])`` + *no description* + +.. _rcparam_axes_xmargin: + +axes.xmargin: ``0.05`` + x margin. See `~.axes.Axes.margins` + +.. _rcparam_axes_ymargin: + +axes.ymargin: ``0.05`` + y margin. See `~.axes.Axes.margins` + +.. _rcparam_axes_zmargin: + +axes.zmargin: ``0.05`` + z margin. See `~.axes.Axes.margins` + +.. _rcparam_axes_autolimit_mode: + +axes.autolimit_mode: ``'data'`` + If "data", use axes.xmargin and axes.ymargin as is. If "round_numbers", after application of margins, axis limits are further expanded to the nearest "round" number. + +.. _rcparam_polaraxes_grid: + +polaraxes.grid: ``True`` + display grid on polar axes + +.. _rcparam_axes3d_grid: + +axes3d.grid: ``True`` + display grid on 3D axes + +.. _rcparam_axes3d_automargin: + +axes3d.automargin: ``False`` + automatically add margin when manually setting 3D axis limits + +.. _rcparam_axes3d_xaxis_panecolor: + +axes3d.xaxis.panecolor: ``(0.95, 0.95, 0.95, 0.5)`` + background pane on 3D axes + +.. _rcparam_axes3d_yaxis_panecolor: + +axes3d.yaxis.panecolor: ``(0.9, 0.9, 0.9, 0.5)`` + background pane on 3D axes + +.. _rcparam_axes3d_zaxis_panecolor: + +axes3d.zaxis.panecolor: ``(0.925, 0.925, 0.925, 0.5)`` + background pane on 3D axes + +.. _rcparam_axes3d_depthshade: + +axes3d.depthshade: ``True`` + depth shade for 3D scatter plots + +.. _rcparam_axes3d_depthshade_minalpha: + +axes3d.depthshade_minalpha: ``0.3`` + minimum alpha value for depth shading + +.. _rcparam_axes3d_mouserotationstyle: + +axes3d.mouserotationstyle: ``'arcball'`` + {azel, trackball, sphere, arcball} See also https://matplotlib.org/stable/api/toolkits/mplot3d/view_angles.html#rotation-with-mouse + +.. _rcparam_axes3d_trackballsize: + +axes3d.trackballsize: ``0.667`` + trackball diameter, in units of the Axes bbox + +.. _rcparam_axes3d_trackballborder: + +axes3d.trackballborder: ``0.2`` + trackball border width, in units of the Axes bbox (only for 'sphere' and 'arcball' style) + +.. _rcparam_xaxis_labellocation: + +xaxis.labellocation: ``'center'`` + alignment of the xaxis label: {left, right, center} + +.. _rcparam_yaxis_labellocation: + +yaxis.labellocation: ``'center'`` + alignment of the yaxis label: {bottom, top, center} + +.. _rcparam_date_autoformatter_year: + +date.autoformatter.year: ``'%Y'`` + *no description* + +.. _rcparam_date_autoformatter_month: + +date.autoformatter.month: ``'%Y-%m'`` + *no description* + +.. _rcparam_date_autoformatter_day: + +date.autoformatter.day: ``'%Y-%m-%d'`` + *no description* + +.. _rcparam_date_autoformatter_hour: + +date.autoformatter.hour: ``'%m-%d %H'`` + *no description* + +.. _rcparam_date_autoformatter_minute: + +date.autoformatter.minute: ``'%d %H:%M'`` + *no description* + +.. _rcparam_date_autoformatter_second: + +date.autoformatter.second: ``'%H:%M:%S'`` + *no description* + +.. _rcparam_date_autoformatter_microsecond: + +date.autoformatter.microsecond: ``'%M:%S.%f'`` + *no description* + +.. _rcparam_date_epoch: + +date.epoch: ``'1970-01-01T00:00:00'`` + The reference date for Matplotlib's internal date representation. See https://matplotlib.org/stable/gallery/ticks/date_precision_and_epochs.html + +.. _rcparam_date_converter: + +date.converter: ``'auto'`` + 'auto', 'concise' + +.. _rcparam_date_interval_multiples: + +date.interval_multiples: ``True`` + For auto converter whether to use interval_multiples + +.. _rcparam_xtick_top: + +xtick.top: ``False`` + draw ticks on the top side + +.. _rcparam_xtick_bottom: + +xtick.bottom: ``True`` + draw ticks on the bottom side + +.. _rcparam_xtick_labeltop: + +xtick.labeltop: ``False`` + draw label on the top + +.. _rcparam_xtick_labelbottom: + +xtick.labelbottom: ``True`` + draw label on the bottom + +.. _rcparam_xtick_major_size: + +xtick.major.size: ``3.5`` + major tick size in points + +.. _rcparam_xtick_minor_size: + +xtick.minor.size: ``2.0`` + minor tick size in points + +.. _rcparam_xtick_major_width: + +xtick.major.width: ``0.8`` + major tick width in points + +.. _rcparam_xtick_minor_width: + +xtick.minor.width: ``0.6`` + minor tick width in points + +.. _rcparam_xtick_major_pad: + +xtick.major.pad: ``3.5`` + distance to major tick label in points + +.. _rcparam_xtick_minor_pad: + +xtick.minor.pad: ``3.4`` + distance to the minor tick label in points + +.. _rcparam_xtick_color: + +xtick.color: ``'black'`` + color of the ticks + +.. _rcparam_xtick_labelcolor: + +xtick.labelcolor: ``'inherit'`` + color of the tick labels or inherit from xtick.color + +.. _rcparam_xtick_labelsize: + +xtick.labelsize: ``'medium'`` + font size of the tick labels + +.. _rcparam_xtick_direction: + +xtick.direction: ``'out'`` + direction: {in, out, inout} + +.. _rcparam_xtick_minor_visible: + +xtick.minor.visible: ``False`` + visibility of minor ticks on x-axis + +.. _rcparam_xtick_major_top: + +xtick.major.top: ``True`` + draw x axis top major ticks + +.. _rcparam_xtick_major_bottom: + +xtick.major.bottom: ``True`` + draw x axis bottom major ticks + +.. _rcparam_xtick_minor_top: + +xtick.minor.top: ``True`` + draw x axis top minor ticks + +.. _rcparam_xtick_minor_bottom: + +xtick.minor.bottom: ``True`` + draw x axis bottom minor ticks + +.. _rcparam_xtick_minor_ndivs: + +xtick.minor.ndivs: ``'auto'`` + number of minor ticks between the major ticks on x-axis + +.. _rcparam_xtick_alignment: + +xtick.alignment: ``'center'`` + alignment of xticks + +.. _rcparam_ytick_left: + +ytick.left: ``True`` + draw ticks on the left side + +.. _rcparam_ytick_right: + +ytick.right: ``False`` + draw ticks on the right side + +.. _rcparam_ytick_labelleft: + +ytick.labelleft: ``True`` + draw tick labels on the left side + +.. _rcparam_ytick_labelright: + +ytick.labelright: ``False`` + draw tick labels on the right side + +.. _rcparam_ytick_major_size: + +ytick.major.size: ``3.5`` + major tick size in points + +.. _rcparam_ytick_minor_size: + +ytick.minor.size: ``2.0`` + minor tick size in points + +.. _rcparam_ytick_major_width: + +ytick.major.width: ``0.8`` + major tick width in points + +.. _rcparam_ytick_minor_width: + +ytick.minor.width: ``0.6`` + minor tick width in points + +.. _rcparam_ytick_major_pad: + +ytick.major.pad: ``3.5`` + distance to major tick label in points + +.. _rcparam_ytick_minor_pad: + +ytick.minor.pad: ``3.4`` + distance to the minor tick label in points + +.. _rcparam_ytick_color: + +ytick.color: ``'black'`` + color of the ticks + +.. _rcparam_ytick_labelcolor: + +ytick.labelcolor: ``'inherit'`` + color of the tick labels or inherit from ytick.color + +.. _rcparam_ytick_labelsize: + +ytick.labelsize: ``'medium'`` + font size of the tick labels + +.. _rcparam_ytick_direction: + +ytick.direction: ``'out'`` + direction: {in, out, inout} + +.. _rcparam_ytick_minor_visible: + +ytick.minor.visible: ``False`` + visibility of minor ticks on y-axis + +.. _rcparam_ytick_major_left: + +ytick.major.left: ``True`` + draw y axis left major ticks + +.. _rcparam_ytick_major_right: + +ytick.major.right: ``True`` + draw y axis right major ticks + +.. _rcparam_ytick_minor_left: + +ytick.minor.left: ``True`` + draw y axis left minor ticks + +.. _rcparam_ytick_minor_right: + +ytick.minor.right: ``True`` + draw y axis right minor ticks + +.. _rcparam_ytick_minor_ndivs: + +ytick.minor.ndivs: ``'auto'`` + number of minor ticks between the major ticks on y-axis + +.. _rcparam_ytick_alignment: + +ytick.alignment: ``'center_baseline'`` + alignment of yticks + +.. _rcparam_grid_color: + +grid.color: ``'#b0b0b0'`` + b0b0b0" # grid color + +.. _rcparam_grid_linestyle: + +grid.linestyle: ``'-'`` + solid + +.. _rcparam_grid_linewidth: + +grid.linewidth: ``0.8`` + in points + +.. _rcparam_grid_alpha: + +grid.alpha: ``1.0`` + transparency, between 0.0 and 1.0 + +.. _rcparam_grid_major_color: + +grid.major.color: ``None`` + If None defaults to grid.color + +.. _rcparam_grid_major_linestyle: + +grid.major.linestyle: ``None`` + If None defaults to grid.linestyle + +.. _rcparam_grid_major_linewidth: + +grid.major.linewidth: ``None`` + If None defaults to grid.linewidth + +.. _rcparam_grid_major_alpha: + +grid.major.alpha: ``None`` + If None defaults to grid.alpha + +.. _rcparam_grid_minor_color: + +grid.minor.color: ``None`` + If None defaults to grid.color + +.. _rcparam_grid_minor_linestyle: + +grid.minor.linestyle: ``None`` + If None defaults to grid.linestyle + +.. _rcparam_grid_minor_linewidth: + +grid.minor.linewidth: ``None`` + If None defaults to grid.linewidth + +.. _rcparam_grid_minor_alpha: + +grid.minor.alpha: ``None`` + If None defaults to grid.alpha + +.. _rcparam_legend_loc: + +legend.loc: ``'best'`` + *no description* + +.. _rcparam_legend_frameon: + +legend.frameon: ``True`` + if True, draw the legend on a background patch + +.. _rcparam_legend_framealpha: + +legend.framealpha: ``0.8`` + legend patch transparency + +.. _rcparam_legend_facecolor: + +legend.facecolor: ``'inherit'`` + inherit from axes.facecolor; or color spec + +.. _rcparam_legend_edgecolor: + +legend.edgecolor: ``'0.8'`` + background patch boundary color + +.. _rcparam_legend_linewidth: + +legend.linewidth: ``None`` + line width of the legend frame, None means inherit from patch.linewidth + +.. _rcparam_legend_fancybox: + +legend.fancybox: ``True`` + if True, use a rounded box for the legend background, else a rectangle + +.. _rcparam_legend_shadow: + +legend.shadow: ``False`` + if True, give background a shadow effect + +.. _rcparam_legend_numpoints: + +legend.numpoints: ``1`` + the number of marker points in the legend line + +.. _rcparam_legend_scatterpoints: + +legend.scatterpoints: ``1`` + number of scatter points + +.. _rcparam_legend_markerscale: + +legend.markerscale: ``1.0`` + the relative size of legend markers vs. original + +.. _rcparam_legend_fontsize: + +legend.fontsize: ``'medium'`` + *no description* + +.. _rcparam_legend_labelcolor: + +legend.labelcolor: ``'None'`` + *no description* + +.. _rcparam_legend_title_fontsize: + +legend.title_fontsize: ``None`` + None sets to the same as the default axes. + +.. _rcparam_legend_borderpad: + +legend.borderpad: ``0.4`` + border whitespace + +.. _rcparam_legend_labelspacing: + +legend.labelspacing: ``0.5`` + the vertical space between the legend entries + +.. _rcparam_legend_handlelength: + +legend.handlelength: ``2.0`` + the length of the legend lines + +.. _rcparam_legend_handleheight: + +legend.handleheight: ``0.7`` + the height of the legend handle + +.. _rcparam_legend_handletextpad: + +legend.handletextpad: ``0.8`` + the space between the legend line and legend text + +.. _rcparam_legend_borderaxespad: + +legend.borderaxespad: ``0.5`` + the border between the axes and legend edge + +.. _rcparam_legend_columnspacing: + +legend.columnspacing: ``2.0`` + column separation + +.. _rcparam_figure_titlesize: + +figure.titlesize: ``'large'`` + size of the figure title (``Figure.suptitle()``) + +.. _rcparam_figure_titleweight: + +figure.titleweight: ``'normal'`` + weight of the figure title + +.. _rcparam_figure_labelsize: + +figure.labelsize: ``'large'`` + size of the figure label (``Figure.sup[x|y]label()``) + +.. _rcparam_figure_labelweight: + +figure.labelweight: ``'normal'`` + weight of the figure label + +.. _rcparam_figure_figsize: + +figure.figsize: ``[6.4, 4.8]`` + figure size in inches + +.. _rcparam_figure_dpi: + +figure.dpi: ``100.0`` + figure dots per inch + +.. _rcparam_figure_facecolor: + +figure.facecolor: ``'white'`` + figure face color + +.. _rcparam_figure_edgecolor: + +figure.edgecolor: ``'white'`` + figure edge color + +.. _rcparam_figure_frameon: + +figure.frameon: ``True`` + enable figure frame + +.. _rcparam_figure_max_open_warning: + +figure.max_open_warning: ``20`` + The maximum number of figures to open through the pyplot interface before emitting a warning. If less than one this feature is disabled. + +.. _rcparam_figure_raise_window: + +figure.raise_window: ``True`` + Raise the GUI window to front when show() is called. If set to False, we currently do not take any further actions and whether the window appears on the front may depend on the GUI framework and window manager. + +.. _rcparam_figure_subplot_left: + +figure.subplot.left: ``0.125`` + the left side of the subplots of the figure + +.. _rcparam_figure_subplot_right: + +figure.subplot.right: ``0.9`` + the right side of the subplots of the figure + +.. _rcparam_figure_subplot_bottom: + +figure.subplot.bottom: ``0.11`` + the bottom of the subplots of the figure + +.. _rcparam_figure_subplot_top: + +figure.subplot.top: ``0.88`` + the top of the subplots of the figure + +.. _rcparam_figure_subplot_wspace: + +figure.subplot.wspace: ``0.2`` + the amount of width reserved for space between subplots, expressed as a fraction of the average axis width + +.. _rcparam_figure_subplot_hspace: + +figure.subplot.hspace: ``0.2`` + the amount of height reserved for space between subplots, expressed as a fraction of the average axis height + +.. _rcparam_figure_autolayout: + +figure.autolayout: ``False`` + When True, automatically adjust subplot parameters to make the plot fit the figure using `~.Figure.tight_layout` + +.. _rcparam_figure_constrained_layout_use: + +figure.constrained_layout.use: ``False`` + When True, automatically make plot elements fit on the figure. (Not compatible with "figure.autolayout", above). + +.. _rcparam_figure_constrained_layout_h_pad: + +figure.constrained_layout.h_pad: ``0.04167`` + Padding (in inches) around axes; defaults to 3/72 inches, i.e. 3 points + +.. _rcparam_figure_constrained_layout_w_pad: + +figure.constrained_layout.w_pad: ``0.04167`` + Padding (in inches) around axes; defaults to 3/72 inches, i.e. 3 points + +.. _rcparam_figure_constrained_layout_hspace: + +figure.constrained_layout.hspace: ``0.02`` + Spacing between subplots, relative to the subplot sizes. Much smaller than for tight_layout (figure.subplot.hspace, figure.subplot.wspace) as constrained_layout already takes surrounding texts (titles, labels, # ticklabels) into account. + +.. _rcparam_figure_constrained_layout_wspace: + +figure.constrained_layout.wspace: ``0.02`` + Spacing between subplots, relative to the subplot sizes. Much smaller than for tight_layout (figure.subplot.hspace, figure.subplot.wspace) as constrained_layout already takes surrounding texts (titles, labels, # ticklabels) into account. + +.. _rcparam_image_aspect: + +image.aspect: ``'equal'`` + {equal, auto} or a number + +.. _rcparam_image_interpolation: + +image.interpolation: ``'auto'`` + see help(imshow) for options + +.. _rcparam_image_interpolation_stage: + +image.interpolation_stage: ``'auto'`` + see help(imshow) for options + +.. _rcparam_image_cmap: + +image.cmap: ``'viridis'`` + A colormap name (plasma, magma, etc.) + +.. _rcparam_image_lut: + +image.lut: ``256`` + the size of the colormap lookup table + +.. _rcparam_image_origin: + +image.origin: ``'upper'`` + {lower, upper} + +.. _rcparam_image_resample: + +image.resample: ``True`` + *no description* + +.. _rcparam_image_composite_image: + +image.composite_image: ``True`` + When True, all the images on a set of axes are combined into a single composite image before saving a figure as a vector graphics file, such as a PDF. + +.. _rcparam_contour_negative_linestyle: + +contour.negative_linestyle: ``'dashed'`` + string or on-off ink sequence + +.. _rcparam_contour_corner_mask: + +contour.corner_mask: ``True`` + {True, False} + +.. _rcparam_contour_linewidth: + +contour.linewidth: ``None`` + {float, None} Size of the contour line widths. If set to None, it falls back to "line.linewidth". + +.. _rcparam_contour_algorithm: + +contour.algorithm: ``'mpl2014'`` + {mpl2005, mpl2014, serial, threaded} + +.. _rcparam_errorbar_capsize: + +errorbar.capsize: ``0.0`` + length of end cap on error bars in pixels + +.. _rcparam_hist_bins: + +hist.bins: ``10`` + The default number of histogram bins or 'auto'. + +.. _rcparam_scatter_marker: + +scatter.marker: ``'o'`` + The default marker type for scatter plots. + +.. _rcparam_scatter_edgecolors: + +scatter.edgecolors: ``'face'`` + The default edge colors for scatter plots. + +.. _rcparam_agg_path_chunksize: + +agg.path.chunksize: ``0`` + 0 to disable; values in the range 10000 to 100000 can improve speed slightly and prevent an Agg rendering failure when plotting very large data sets, especially if they are very gappy. It may cause minor artifacts, though. A value of 20000 is probably a good starting point. + +.. _rcparam_path_simplify: + +path.simplify: ``True`` + When True, simplify paths by removing "invisible" points to reduce file size and increase rendering speed + +.. _rcparam_path_simplify_threshold: + +path.simplify_threshold: ``0.111111111111`` + The threshold of similarity below which vertices will be removed in the simplification process. + +.. _rcparam_path_snap: + +path.snap: ``True`` + When True, rectilinear axis-aligned paths will be snapped to the nearest pixel when certain criteria are met. When False, paths will never be snapped. + +.. _rcparam_path_sketch: + +path.sketch: ``None`` + May be None, or a tuple of the form:path.sketch: (scale, length, randomness)- *scale* is the amplitude of the wiggle perpendicular to the line (in pixels).- *length* is the length of the wiggle along the line (in pixels).- *randomness* is the factor by which the length is randomly scaled. + +.. _rcparam_path_effects: + +path.effects: ``[]`` + *no description* + +.. _rcparam_savefig_dpi: + +savefig.dpi: ``'figure'`` + figure dots per inch or 'figure' + +.. _rcparam_savefig_facecolor: + +savefig.facecolor: ``'auto'`` + figure face color when saving + +.. _rcparam_savefig_edgecolor: + +savefig.edgecolor: ``'auto'`` + figure edge color when saving + +.. _rcparam_savefig_format: + +savefig.format: ``'png'`` + {png, ps, pdf, svg} + +.. _rcparam_savefig_bbox: + +savefig.bbox: ``None`` + {tight, standard} 'tight' is incompatible with generating frames for animation + +.. _rcparam_savefig_pad_inches: + +savefig.pad_inches: ``0.1`` + padding to be used, when bbox is set to 'tight' + +.. _rcparam_savefig_directory: + +savefig.directory: ``'~'`` + default directory in savefig dialog, gets updated after interactive saves, unless set to the empty string (i.e. the current directory); use '.' to start at the current directory but update after interactive saves + +.. _rcparam_savefig_transparent: + +savefig.transparent: ``False`` + whether figures are saved with a transparent background by default + +.. _rcparam_savefig_orientation: + +savefig.orientation: ``'portrait'`` + orientation of saved figure, for PostScript output only + +.. _rcparam_macosx_window_mode: + +macosx.window_mode: ``'system'`` + How to open new figures (system, tab, window) system uses the MacOS system preferences + +.. _rcparam_tk_window_focus: + +tk.window_focus: ``False`` + Maintain shell focus for TkAgg + +.. _rcparam_ps_papersize: + +ps.papersize: ``'letter'`` + {figure, letter, legal, ledger, A0-A10, B0-B10} + +.. _rcparam_ps_useafm: + +ps.useafm: ``False`` + use AFM fonts, results in small files + +.. _rcparam_ps_usedistiller: + +ps.usedistiller: ``None`` + {ghostscript, xpdf, None} Experimental: may produce smaller files. xpdf intended for production of publication quality files, but requires ghostscript, xpdf and ps2eps + +.. _rcparam_ps_distiller_res: + +ps.distiller.res: ``6000`` + dpi + +.. _rcparam_ps_fonttype: + +ps.fonttype: ``3`` + Output Type 3 (Type3) or Type 42 (TrueType) + +.. _rcparam_pdf_compression: + +pdf.compression: ``6`` + integer from 0 to 9 0 disables compression (good for debugging) + +.. _rcparam_pdf_fonttype: + +pdf.fonttype: ``3`` + Output Type 3 (Type3) or Type 42 (TrueType) + +.. _rcparam_pdf_use14corefonts: + +pdf.use14corefonts: ``False`` + *no description* + +.. _rcparam_pdf_inheritcolor: + +pdf.inheritcolor: ``False`` + *no description* + +.. _rcparam_svg_image_inline: + +svg.image_inline: ``True`` + Write raster image data directly into the SVG file + +.. _rcparam_svg_fonttype: + +svg.fonttype: ``'path'`` + How to handle SVG fonts: path: Embed characters as paths -- supported by most SVG renderersnone: Assume fonts are installed on the machine where the SVG will be viewed. + +.. _rcparam_svg_hashsalt: + +svg.hashsalt: ``None`` + If not None, use this string as hash salt instead of uuid4 + +.. _rcparam_svg_id: + +svg.id: ``None`` + If not None, use this string as the value for the `id` attribute in the top tag + +.. _rcparam_pgf_rcfonts: + +pgf.rcfonts: ``True`` + *no description* + +.. _rcparam_pgf_preamble: + +pgf.preamble: ``''`` + See text.latex.preamble for documentation + +.. _rcparam_pgf_texsystem: + +pgf.texsystem: ``'xelatex'`` + *no description* + +.. _rcparam_docstring_hardcopy: + +docstring.hardcopy: ``False`` + set this when you want to generate hardcopy docstring + +.. _rcparam_keymap_fullscreen: + +keymap.fullscreen: ``['f', 'ctrl+f']`` + toggling + +.. _rcparam_keymap_home: + +keymap.home: ``['h', 'r', 'home']`` + home or reset mnemonic + +.. _rcparam_keymap_back: + +keymap.back: ``['left', 'c', 'backspace', 'MouseButton.BACK']`` + forward / backward keys + +.. _rcparam_keymap_forward: + +keymap.forward: ``['right', 'v', 'MouseButton.FORWARD']`` + for quick navigation + +.. _rcparam_keymap_pan: + +keymap.pan: ``['p']`` + pan mnemonic + +.. _rcparam_keymap_zoom: + +keymap.zoom: ``['o']`` + zoom mnemonic + +.. _rcparam_keymap_save: + +keymap.save: ``['s', 'ctrl+s']`` + saving current figure + +.. _rcparam_keymap_help: + +keymap.help: ``['f1']`` + display help about active tools + +.. _rcparam_keymap_quit: + +keymap.quit: ``['ctrl+w', 'cmd+w', 'q']`` + close the current figure + +.. _rcparam_keymap_quit_all: + +keymap.quit_all: ``[]`` + close all figures + +.. _rcparam_keymap_grid: + +keymap.grid: ``['g']`` + switching on/off major grids in current axes + +.. _rcparam_keymap_grid_minor: + +keymap.grid_minor: ``['G']`` + switching on/off minor grids in current axes + +.. _rcparam_keymap_yscale: + +keymap.yscale: ``['l']`` + toggle scaling of y-axes ('log'/'linear') + +.. _rcparam_keymap_xscale: + +keymap.xscale: ``['k', 'L']`` + toggle scaling of x-axes ('log'/'linear') + +.. _rcparam_keymap_copy: + +keymap.copy: ``['ctrl+c', 'cmd+c']`` + copy figure to clipboard + +.. _rcparam_animation_html: + +animation.html: ``'none'`` + How to display the animation as HTML in the IPython notebook: - 'html5' uses HTML5 video tag - 'jshtml' creates a JavaScript animation + +.. _rcparam_animation_writer: + +animation.writer: ``'ffmpeg'`` + MovieWriter 'backend' to use + +.. _rcparam_animation_codec: + +animation.codec: ``'h264'`` + Codec to use for writing movie + +.. _rcparam_animation_bitrate: + +animation.bitrate: ``-1`` + Controls size/quality trade-off for movie. -1 implies let utility auto-determine + +.. _rcparam_animation_frame_format: + +animation.frame_format: ``'png'`` + Controls frame format used by temp files + +.. _rcparam_animation_ffmpeg_path: + +animation.ffmpeg_path: ``'ffmpeg'`` + Path to ffmpeg binary. Unqualified paths are resolved by subprocess.Popen. + +.. _rcparam_animation_ffmpeg_args: + +animation.ffmpeg_args: ``[]`` + Additional arguments to pass to ffmpeg + +.. _rcparam_animation_convert_path: + +animation.convert_path: ``'convert'`` + Path to ImageMagick's convert binary. Unqualified paths are resolved by subprocess.Popen, except that on Windows, we look up an install of ImageMagick in the registry (as convert is also the name of a system tool). + +.. _rcparam_animation_convert_args: + +animation.convert_args: ``['-layers', 'OptimizePlus']`` + Additional arguments to pass to convert + +.. _rcparam_animation_embed_limit: + +animation.embed_limit: ``20.0`` + Limit, in MB, of size of base64 encoded animation in HTML (i.e. IPython notebook) + +.. _rcparam__internal_classic_mode: + +_internal.classic_mode: ``False`` + *no description* + +.. _rcparam_backend: + +backend: ``None`` + *no description* diff --git a/doc/users/index.rst b/doc/users/index.rst index 733f176e556c..b98bda824a7e 100644 --- a/doc/users/index.rst +++ b/doc/users/index.rst @@ -59,6 +59,7 @@ Using Matplotlib :maxdepth: 2 :includehidden: + explain/configuration explain/customizing .. grid-item-card:: diff --git a/extern/agg24-svn/include/agg_image_filters.h b/extern/agg24-svn/include/agg_image_filters.h index 8e1bc8f0dba4..e5b813dfc8a6 100644 --- a/extern/agg24-svn/include/agg_image_filters.h +++ b/extern/agg24-svn/include/agg_image_filters.h @@ -53,8 +53,13 @@ namespace agg double r = filter.radius(); realloc_lut(r); unsigned i; +#ifndef MPL_FIX_AGG_IMAGE_FILTER_LUT_BUGS unsigned pivot = diameter() << (image_subpixel_shift - 1); for(i = 0; i < pivot; i++) +#else + unsigned pivot = (diameter() << (image_subpixel_shift - 1)) - 1; + for(i = 0; i < pivot + 1; i++) +#endif { double x = double(i) / double(image_subpixel_scale); double y = filter.calc_weight(x); @@ -62,7 +67,11 @@ namespace agg m_weight_array[pivot - i] = (int16)iround(y * image_filter_scale); } unsigned end = (diameter() << image_subpixel_shift) - 1; +#ifndef MPL_FIX_AGG_IMAGE_FILTER_LUT_BUGS m_weight_array[0] = m_weight_array[end]; +#else + m_weight_array[end] = (int16)iround(filter.calc_weight(diameter() / 2) * image_filter_scale); +#endif if(normalization) { normalize(); diff --git a/extern/agg24-svn/include/agg_span_interpolator_linear.h b/extern/agg24-svn/include/agg_span_interpolator_linear.h index ef10505ce11a..39cc7610b00c 100644 --- a/extern/agg24-svn/include/agg_span_interpolator_linear.h +++ b/extern/agg24-svn/include/agg_span_interpolator_linear.h @@ -53,6 +53,10 @@ namespace agg //---------------------------------------------------------------- void begin(double x, double y, unsigned len) { +#ifdef MPL_FIX_AGG_INTERPOLATION_ENDPOINT_BUG + len -= 1; +#endif + double tx; double ty; @@ -75,6 +79,10 @@ namespace agg //---------------------------------------------------------------- void resynchronize(double xe, double ye, unsigned len) { +#ifdef MPL_FIX_AGG_INTERPOLATION_ENDPOINT_BUG + len -= 1; +#endif + m_trans->transform(&xe, &ye); m_li_x = dda2_line_interpolator(m_li_x.y(), iround(xe * subpixel_scale), len); m_li_y = dda2_line_interpolator(m_li_y.y(), iround(ye * subpixel_scale), len); diff --git a/extern/agg24-svn/src/agg_image_filters.cpp b/extern/agg24-svn/src/agg_image_filters.cpp index 549d9adbf5af..1571308be55a 100644 --- a/extern/agg24-svn/src/agg_image_filters.cpp +++ b/extern/agg24-svn/src/agg_image_filters.cpp @@ -88,6 +88,7 @@ namespace agg } } +#ifndef MPL_FIX_AGG_IMAGE_FILTER_LUT_BUGS unsigned pivot = m_diameter << (image_subpixel_shift - 1); for(i = 0; i < pivot; i++) @@ -96,6 +97,7 @@ namespace agg } unsigned end = (diameter() << image_subpixel_shift) - 1; m_weight_array[0] = m_weight_array[end]; +#endif } diff --git a/galleries/examples/axisartist/demo_floating_axes.py b/galleries/examples/axisartist/demo_floating_axes.py index e2218ae7a4c5..d36b534161c1 100644 --- a/galleries/examples/axisartist/demo_floating_axes.py +++ b/galleries/examples/axisartist/demo_floating_axes.py @@ -56,21 +56,18 @@ def setup_axes2(fig, rect): tr = PolarAxes.PolarTransform() pi = np.pi - angle_ticks = [(0, r"$0$"), - (.25*pi, r"$\frac{1}{4}\pi$"), - (.5*pi, r"$\frac{1}{2}\pi$")] - grid_locator1 = FixedLocator([v for v, s in angle_ticks]) - tick_formatter1 = DictFormatter(dict(angle_ticks)) - - grid_locator2 = MaxNLocator(2) - + angle_ticks = { + 0: r"$0$", + pi/4: r"$\frac{1}{4}\pi$", + pi/2: r"$\frac{1}{2}\pi$", + } grid_helper = floating_axes.GridHelperCurveLinear( tr, extremes=(.5*pi, 0, 2, 1), - grid_locator1=grid_locator1, - grid_locator2=grid_locator2, - tick_formatter1=tick_formatter1, - tick_formatter2=None) - + grid_locator1=FixedLocator([*angle_ticks]), + tick_formatter1=DictFormatter(angle_ticks), + grid_locator2=MaxNLocator(2), + tick_formatter2=None, + ) ax1 = fig.add_subplot( rect, axes_class=floating_axes.FloatingAxes, grid_helper=grid_helper) ax1.grid() @@ -92,30 +89,22 @@ def setup_axes3(fig, rect): Sometimes, things like axis_direction need to be adjusted. """ - # rotate a bit for better orientation - tr_rotate = Affine2D().translate(-95, 0) - - # scale degree to radians - tr_scale = Affine2D().scale(np.pi/180., 1.) - + tr_rotate = Affine2D().translate(-95, 0) # rotate a bit for better orientation + tr_scale = Affine2D().scale(np.pi/180., 1.) # scale degree to radians tr = tr_rotate + tr_scale + PolarAxes.PolarTransform() - grid_locator1 = angle_helper.LocatorHMS(4) - tick_formatter1 = angle_helper.FormatterHMS() - - grid_locator2 = MaxNLocator(3) - # Specify theta limits in degrees ra0, ra1 = 8.*15, 14.*15 # Specify radial limits cz0, cz1 = 0, 14000 + grid_helper = floating_axes.GridHelperCurveLinear( tr, extremes=(ra0, ra1, cz0, cz1), - grid_locator1=grid_locator1, - grid_locator2=grid_locator2, - tick_formatter1=tick_formatter1, - tick_formatter2=None) - + grid_locator1=angle_helper.LocatorHMS(4), + tick_formatter1=angle_helper.FormatterHMS(), + grid_locator2=MaxNLocator(3), + tick_formatter2=None, + ) ax1 = fig.add_subplot( rect, axes_class=floating_axes.FloatingAxes, grid_helper=grid_helper) diff --git a/galleries/examples/axisartist/demo_floating_axis.py b/galleries/examples/axisartist/demo_floating_axis.py index 0894bf8f4ce1..7760ed2089be 100644 --- a/galleries/examples/axisartist/demo_floating_axis.py +++ b/galleries/examples/axisartist/demo_floating_axis.py @@ -22,29 +22,19 @@ def curvelinear_test2(fig): """Polar projection, but in a rectangular box.""" # see demo_curvelinear_grid.py for details - tr = Affine2D().scale(np.pi / 180., 1.) + PolarAxes.PolarTransform() - - extreme_finder = angle_helper.ExtremeFinderCycle(20, - 20, - lon_cycle=360, - lat_cycle=None, - lon_minmax=None, - lat_minmax=(0, np.inf), - ) - - grid_locator1 = angle_helper.LocatorDMS(12) - - tick_formatter1 = angle_helper.FormatterDMS() - - grid_helper = GridHelperCurveLinear(tr, - extreme_finder=extreme_finder, - grid_locator1=grid_locator1, - tick_formatter1=tick_formatter1 - ) - + grid_helper = GridHelperCurveLinear( + Affine2D().scale(np.pi / 180., 1.) + PolarAxes.PolarTransform(), + extreme_finder=angle_helper.ExtremeFinderCycle( + 20, 20, + lon_cycle=360, lat_cycle=None, + lon_minmax=None, lat_minmax=(0, np.inf), + ), + grid_locator1=angle_helper.LocatorDMS(12), + tick_formatter1=angle_helper.FormatterDMS(), + ) ax1 = fig.add_subplot(axes_class=HostAxes, grid_helper=grid_helper) - # Now creates floating axis + # Now create floating axis # floating axis whose first coordinate (theta) is fixed at 60 ax1.axis["lat"] = axis = ax1.new_floating_axis(0, 60) diff --git a/galleries/examples/color/color_demo.py b/galleries/examples/color/color_demo.py index 6822efc3faa7..b8b06c9091e3 100644 --- a/galleries/examples/color/color_demo.py +++ b/galleries/examples/color/color_demo.py @@ -16,7 +16,7 @@ 5) a single letter string, i.e. one of ``{'b', 'g', 'r', 'c', 'm', 'y', 'k', 'w'}``, which are short-hand notations for shades of blue, green, red, cyan, magenta, yellow, black, and white; -6) a X11/CSS4 ("html") color name, e.g. ``"blue"``; +6) an X11/CSS4 ("html") color name, e.g. ``"blue"``; 7) a name from the `xkcd color survey `__, prefixed with ``'xkcd:'`` (e.g., ``'xkcd:sky blue'``); 8) a "Cn" color spec, i.e. ``'C'`` followed by a number, which is an index into diff --git a/galleries/examples/lines_bars_and_markers/barh.py b/galleries/examples/lines_bars_and_markers/barh.py index 5493c7456c75..8529698c1ddb 100644 --- a/galleries/examples/lines_bars_and_markers/barh.py +++ b/galleries/examples/lines_bars_and_markers/barh.py @@ -6,22 +6,16 @@ This example showcases a simple horizontal bar chart. """ import matplotlib.pyplot as plt -import numpy as np - -# Fixing random state for reproducibility -np.random.seed(19680801) fig, ax = plt.subplots() # Example data people = ('Tom', 'Dick', 'Harry', 'Slim', 'Jim') -y_pos = np.arange(len(people)) -performance = 3 + 10 * np.random.rand(len(people)) -error = np.random.rand(len(people)) +performance = [5, 7, 6, 4, 9] +error = [0.2, 0.4, 0.3, 0.6, 0.2] -ax.barh(y_pos, performance, xerr=error, align='center') -ax.set_yticks(y_pos, labels=people) -ax.invert_yaxis() # labels read top-to-bottom +ax.barh(people, performance, xerr=error, align='center') +ax.yaxis.set_inverted(True) # arrange data from top to bottom ax.set_xlabel('Performance') ax.set_title('How fast do you want to go today?') diff --git a/galleries/examples/mplot3d/intersecting_planes.py b/galleries/examples/mplot3d/intersecting_planes.py index a5a92caf5c6b..4f42e7bbeb94 100644 --- a/galleries/examples/mplot3d/intersecting_planes.py +++ b/galleries/examples/mplot3d/intersecting_planes.py @@ -12,7 +12,7 @@ example, we lift the problem of mutual overlap by segmenting the planes at their intersections, making four parts out of each plane. -This examples only works correctly for planes that cut each other in haves. This +This examples only works correctly for planes that cut each other in halves. This limitation is intentional to keep the code more readable. Cutting at arbitrary positions would of course be possible but makes the code even more complex. Thus, this example is more a demonstration of the concept how to work around diff --git a/galleries/examples/specialty_plots/skewt.py b/galleries/examples/specialty_plots/skewt.py index 04d36c79f067..3a9c14ca6111 100644 --- a/galleries/examples/specialty_plots/skewt.py +++ b/galleries/examples/specialty_plots/skewt.py @@ -34,9 +34,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) diff --git a/galleries/examples/statistics/boxplot_vs_violin.py b/galleries/examples/statistics/boxplot_vs_violin.py index f277e737e65c..06aa2693f446 100644 --- a/galleries/examples/statistics/boxplot_vs_violin.py +++ b/galleries/examples/statistics/boxplot_vs_violin.py @@ -12,12 +12,15 @@ the whole range of the data. A good general reference on boxplots and their history can be found -here: http://vita.had.co.nz/papers/boxplots.pdf +here: https://vita.had.co.nz/papers/boxplots.pdf Violin plots require matplotlib >= 1.4. -For more information on violin plots, the scikit-learn docs have a great -section: https://scikit-learn.org/stable/modules/density.html +Violin plots show the distribution of the data as a rotated kernel density +estimate (KDE) along with summary statistics similar to a box plot. + +For more information on violin plots, see: +https://en.wikipedia.org/wiki/Violin_plot """ import matplotlib.pyplot as plt diff --git a/galleries/examples/lines_bars_and_markers/cohere.py b/galleries/examples/statistics/cohere.py similarity index 94% rename from galleries/examples/lines_bars_and_markers/cohere.py rename to galleries/examples/statistics/cohere.py index f02788ea1d69..4fd76d66a06a 100644 --- a/galleries/examples/lines_bars_and_markers/cohere.py +++ b/galleries/examples/statistics/cohere.py @@ -4,6 +4,8 @@ ===================================== An example showing how to plot the coherence of two signals using `~.Axes.cohere`. + +.. redirect-from:: /gallery/lines_bars_and_markers/cohere """ import matplotlib.pyplot as plt import numpy as np diff --git a/galleries/examples/lines_bars_and_markers/csd_demo.py b/galleries/examples/statistics/csd_demo.py similarity index 94% rename from galleries/examples/lines_bars_and_markers/csd_demo.py rename to galleries/examples/statistics/csd_demo.py index 76d9f0825223..31c657f6b47f 100644 --- a/galleries/examples/lines_bars_and_markers/csd_demo.py +++ b/galleries/examples/statistics/csd_demo.py @@ -4,6 +4,8 @@ ============================ Plot the cross spectral density (CSD) of two signals using `~.Axes.csd`. + +.. redirect-from:: /gallery/lines_bars_and_markers/csd_demo """ import matplotlib.pyplot as plt import numpy as np diff --git a/galleries/examples/lines_bars_and_markers/psd_demo.py b/galleries/examples/statistics/psd_demo.py similarity index 98% rename from galleries/examples/lines_bars_and_markers/psd_demo.py rename to galleries/examples/statistics/psd_demo.py index edbfc79289af..bf564df7542c 100644 --- a/galleries/examples/lines_bars_and_markers/psd_demo.py +++ b/galleries/examples/statistics/psd_demo.py @@ -8,6 +8,8 @@ The PSD is a common plot in the field of signal processing. NumPy has many useful libraries for computing a PSD. Below we demo a few examples of how this can be accomplished and visualized with Matplotlib. + +.. redirect-from:: /gallery/lines_bars_and_markers/psd_demo """ import matplotlib.pyplot as plt import numpy as np diff --git a/galleries/examples/lines_bars_and_markers/xcorr_acorr_demo.py b/galleries/examples/statistics/xcorr_acorr_demo.py similarity index 93% rename from galleries/examples/lines_bars_and_markers/xcorr_acorr_demo.py rename to galleries/examples/statistics/xcorr_acorr_demo.py index 7878ef8d7468..f0cd0ecaf8ed 100644 --- a/galleries/examples/lines_bars_and_markers/xcorr_acorr_demo.py +++ b/galleries/examples/statistics/xcorr_acorr_demo.py @@ -5,6 +5,8 @@ Example use of cross-correlation (`~.Axes.xcorr`) and auto-correlation (`~.Axes.acorr`) plots. + +.. redirect-from:: /gallery/lines_bars_and_markers/xcorr_acorr_demo """ import matplotlib.pyplot as plt import numpy as np diff --git a/galleries/examples/text_labels_and_annotations/annotate_transform.py b/galleries/examples/text_labels_and_annotations/annotate_transform.py deleted file mode 100644 index e7d4e11d9d38..000000000000 --- a/galleries/examples/text_labels_and_annotations/annotate_transform.py +++ /dev/null @@ -1,54 +0,0 @@ -""" -================== -Annotate transform -================== - -This example shows how to use different coordinate systems for annotations. -For a complete overview of the annotation capabilities, also see the -:ref:`annotation tutorial`. - -.. redirect-from:: /gallery/pyplots/annotate_transform -""" - -import matplotlib.pyplot as plt -import numpy as np - -x = np.arange(0, 10, 0.005) -y = np.exp(-x/2.) * np.sin(2*np.pi*x) - -fig, ax = plt.subplots() -ax.plot(x, y) -ax.set_xlim(0, 10) -ax.set_ylim(-1, 1) - -xdata, ydata = 5, 0 -xdisplay, ydisplay = ax.transData.transform((xdata, ydata)) - -bbox = dict(boxstyle="round", fc="0.8") -arrowprops = dict( - arrowstyle="->", - connectionstyle="angle,angleA=0,angleB=90,rad=10") - -offset = 72 -ax.annotate( - f'data = ({xdata:.1f}, {ydata:.1f})', - (xdata, ydata), - xytext=(-2*offset, offset), textcoords='offset points', - bbox=bbox, arrowprops=arrowprops) -ax.annotate( - f'display = ({xdisplay:.1f}, {ydisplay:.1f})', - xy=(xdisplay, ydisplay), xycoords='figure pixels', - xytext=(0.5*offset, -offset), textcoords='offset points', - bbox=bbox, arrowprops=arrowprops) - -plt.show() - -# %% -# -# .. admonition:: References -# -# The use of the following functions, methods, classes and modules is shown -# in this example: -# -# - `matplotlib.transforms.Transform.transform` -# - `matplotlib.axes.Axes.annotate` / `matplotlib.pyplot.annotate` diff --git a/galleries/examples/text_labels_and_annotations/multiline.py b/galleries/examples/text_labels_and_annotations/multiline.py index 2aa6fea8c1af..e9ce81fe6526 100644 --- a/galleries/examples/text_labels_and_annotations/multiline.py +++ b/galleries/examples/text_labels_and_annotations/multiline.py @@ -11,7 +11,7 @@ ax0.set_aspect(1) ax0.plot(np.arange(10)) -ax0.set_xlabel('this is a xlabel\n(with newlines!)') +ax0.set_xlabel('this is an xlabel\n(with newlines!)') ax0.set_ylabel('this is vertical\ntest', multialignment='center') ax0.text(2, 7, 'this is\nyet another test', rotation=45, diff --git a/galleries/tutorials/coding_shortcuts.py b/galleries/tutorials/coding_shortcuts.py new file mode 100644 index 000000000000..46868482598f --- /dev/null +++ b/galleries/tutorials/coding_shortcuts.py @@ -0,0 +1,172 @@ +""" +================ +Coding shortcuts +================ + +Matplotlib's primary and universal API is the :ref:`Axes interface `. +While it is clearly structured and powerful, it can sometimes feel overly verbose and +thus cumbersome to write. This page collects patterns for condensing the code +of the Axes-based API and achieving the same results with less typing for many simpler +plots. + +.. note:: + + The :ref:`pyplot interface ` is an alternative more compact + interface, and was historically modeled to be similar to MATLAB. It remains a + valid approach for those who want to use it. However, it has the disadvantage that + it achieves its brevity through implicit assumptions that you have to understand. + + Since it follows a different paradigm, switching between the Axes interface and + the pyplot interface requires a shift of the mental model, and some code rewrite, + if the code develops to a point at which pyplot no longer provides enough + flexibility. + +This tutorial goes the other way round, starting from the standard verbose Axes +interface and using its capabilities for shortcuts when you don't need all the +generality. + +Let's assume we want to make a plot of the number of daylight hours per day over the +year in London. + +The standard approach with the Axes interface looks like this. +""" + +import matplotlib.pyplot as plt +import numpy as np + +day = np.arange(365) +hours = 4.276 * np.sin(2 * np.pi * (day - 80)/365) + 12.203 + +fig, ax = plt.subplots() +ax.plot(day, hours, color="orange") +ax.set_xlabel("day") +ax.set_ylabel("daylight hours") +ax.set_title("London") +plt.show() + +# %% +# Note that we've included ``plt.show()`` here. This is needed to show the plot window +# when running from a command line or in a Python script. If you run a Jupyter notebook, +# this command is automatically executed at the end of each cell. +# +# For the rest of the tutorial, we'll assume that we are in a notebook and leave this +# out for brevity. Depending on your context you may still need it. +# +# If you instead want to save to a file, use ``fig.savefig("daylight.png")``. +# +# +# Collect Axes properties into a single ``set()`` call +# ==================================================== +# +# The properties of Matplotlib Artists can be modified through their respective +# ``set_*()`` methods. Artists additionally have a generic ``set()`` method, that takes +# keyword arguments and is equivalent to calling all the respective ``set_*()`` methods. +# :: +# +# ax.set_xlabel("day") +# ax.set_ylabel("daylight hours") +# +# can also be written as :: +# +# ax.set(xlabel="day", ylabel="daylight hours") +# +# This is the most simple and effective reduction you can do. With that we can shorten +# the above plot to + +fig, ax = plt.subplots() +ax.plot(day, hours, color="orange") +ax.set(xlabel="day", ylabel="daylight hours", title="London") + +# %% +# +# This works as long as you only need to pass one parameter to the ``set_*()`` function. +# The individual functions are still necessary if you want more control, e.g. +# ``set_title("London", fontsize=16)``. +# +# +# Not storing a reference to the figure +# ===================================== +# Another nuisance of ``fig, ax = plt.subplots()`` is that you always create a ``fig`` +# variable, even if you don't use it. A slightly shorter version would be using the +# standard variable for unused value in Python (``_``): ``_, ax = plt.subplots()``. +# However, that's only marginally better. +# +# You can work around this by separating figure and Axes creation and chaining them :: +# +# ax = plt.figure().add_subplot() +# +# This is a bit cleaner logically and has the slight advantage that you could set +# figure properties inline as well; e.g. ``plt.figure(facecolor="lightgoldenrod")``. +# But it has the disadvantage that it's longer than ``fig, ax = plt.subplots()``. +# +# You can still obtain the figure from the Axes if needed, e.g. :: +# +# ax.figure.savefig("daylight_hours.png") +# +# The example code now looks like this: + +ax = plt.figure().add_subplot() +ax.plot(day, hours, color="orange") +ax.set(xlabel="day", ylabel="daylight hours", title="London") + +# %% +# Define Axes properties during axes creation +# =========================================== +# The ``set_*`` methods as well as ``set`` modify existing objects. You can +# alternatively define them right at creation. Since you typically don't instantiate +# classes yourself in Matplotlib, but rather call some factory function that creates +# the object and wires it up correctly with the plot, this may seem less obvious. But +# in fact you just pass the desired properties to the factory functions. You are likely +# doing this already in some places without realizing. Consider the function to create +# a line :: +# +# ax.plot(x, y, color="orange") +# +# This is equivalent to :: +# +# line, = ax.plot(x, y) +# line.set_color("orange") +# +# The same can be done with functions that create Axes. + +ax = plt.figure().add_subplot(xlabel="day", ylabel="daylight hours", title="London") +ax.plot(day, hours, color="orange") + +# %% +# .. important:: +# The Axes properties are only accepted as keyword arguments by +# `.Figure.add_subplot`, which creates a single Axes. +# +# For `.Figure.subplots` and `.pyplot.subplots`, you'd have to pass the properties +# as a dict via the keyword argument ``subplot_kw``. The limitation here is that +# such parameters are given to all Axes. For example, if you need two subplots +# (``fig, (ax1, ax2) = plt.subplots(1, 2)``) with different labels, you have to +# set them individually. +# +# Defining Axes properties during creation is best used for single subplots or when +# all subplots share the same properties. +# +# +# Using implicit figure creation +# ============================== +# You can go even further by tapping into the pyplot logic and use `.pyplot.axes` to +# create the axes: + +ax = plt.axes(xlabel="day", ylabel="daylight hours", title="London") +ax.plot(day, hours, color="orange") + +# %% +# .. warning:: +# When using this, you have to be aware of the implicit figure semantics of pyplot. +# ``plt.axes()`` will only create a new figure if no figure exists. Otherwise, it +# will add the Axes to the current existing figure, which is likely not what you +# want. +# +# Not storing a reference to the Axes +# =================================== +# If you only need to visualize one dataset, you can append the plot command +# directly to the Axes creation. This may be useful e.g. in notebooks, +# where you want to create a plot with some configuration, but as little distracting +# code as possible: + +plt.axes(xlabel="day", ylabel="daylight hours").plot(day, hours, color="orange") diff --git a/galleries/tutorials/index.rst b/galleries/tutorials/index.rst index 48187a862a2e..76c0037dca11 100644 --- a/galleries/tutorials/index.rst +++ b/galleries/tutorials/index.rst @@ -32,6 +32,23 @@ a :ref:`FAQ ` in our :ref:`user guide `. +.. 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/users_explain/configuration.py b/galleries/users_explain/configuration.py new file mode 100644 index 000000000000..d5f7f38e98ff --- /dev/null +++ b/galleries/users_explain/configuration.py @@ -0,0 +1,14 @@ +""" +.. _rcparams_reference: + +Matplotlib configuration - rcParams +=================================== +Matplotlib's configuration parameters (rcParams) control the behavior and +appearance of plots. These parameters are stored in a dict-like variable +called :data:`matplotlib.rcParams` (also accessible via ``plt.rcParams``). + +The following is a comprehensive reference of all available rcParams and their +default values. + +.. include:: /users/_rcparams_generated.rst +""" diff --git a/galleries/users_explain/customizing.py b/galleries/users_explain/customizing.py index 05b75ba7d0a4..948727c6a165 100644 --- a/galleries/users_explain/customizing.py +++ b/galleries/users_explain/customizing.py @@ -8,9 +8,12 @@ Customizing Matplotlib with style sheets and rcParams ===================================================== -Tips for customizing the properties and default styles of Matplotlib. +Many aspects of Matplotlib's behavior and default styles can be customized +through the use of rc (runtime configuration) settings. The current values +are stored in `~matplotlib.rcParams`. -There are three ways to customize Matplotlib: +There are three ways to customize Matplotlib, all of which effectively change +`~matplotlib.rcParams`: 1. :ref:`Setting rcParams at runtime`. 2. :ref:`Using style sheets`. diff --git a/galleries/users_explain/figure/backends.rst b/galleries/users_explain/figure/backends.rst index 85ed8dece23d..69f6d61dc563 100644 --- a/galleries/users_explain/figure/backends.rst +++ b/galleries/users_explain/figure/backends.rst @@ -253,6 +253,35 @@ backend, use ``module://name.of.the.backend`` as the backend name, e.g. Information for backend implementers is available at :ref:`writing_backend_interface`. +Backend API versions +-------------------- +Matplotlib aims to maintain backward compatibility on backends. Nevertheless, we +want to be able to evolve the backend API to support new features. Defining backend +API versions will help to communicate which API is supported by a given version of +Matplotlib. + +The following backend API versions exist + +.. list-table:: + :header-rows: 1 + + * - API version + - Supported since + - Description + * - 1.0 + - Matplotlib 3.10 + - This is the starting point for systematic definition of backend versions. + Most of the API will work far back, but there is no benefit in retroactively + uncovering all prior the changes. + * - 1.1 + - Matplotlib 3.11 + - `.RendererBase.draw_path_collection` gained a new optional parameter + *hatchcolor*. The presence of the parameter is inferred by introspection, so + that matplotlib 3.11+ will still work with backends implementing API version + 1.0. + +There is currently no plan to remove support for older API versions. + .. _figures-not-showing: Debugging the figure windows not showing diff --git a/galleries/users_explain/figure/event_handling.rst b/galleries/users_explain/figure/event_handling.rst index caf987e3d6da..8b4928eafb52 100644 --- a/galleries/users_explain/figure/event_handling.rst +++ b/galleries/users_explain/figure/event_handling.rst @@ -605,7 +605,7 @@ Picking exercise Create a data set of 100 arrays of 1000 Gaussian random numbers and compute the sample mean and standard deviation of each of them (hint: -NumPy arrays have a mean and std method) and make a xy marker plot of +NumPy arrays have a mean and std method) and make an xy marker plot of the 100 means vs. the 100 standard deviations. Connect the line created by the plot command to the pick event, and plot the original time series of the data that generated the clicked on points. If more diff --git a/galleries/users_explain/text/annotations.py b/galleries/users_explain/text/annotations.py index 5cfb16c12715..b0eff8d19f7d 100644 --- a/galleries/users_explain/text/annotations.py +++ b/galleries/users_explain/text/annotations.py @@ -12,6 +12,7 @@ .. redirect-from:: /gallery/userdemo/connect_simple01 .. redirect-from:: /gallery/userdemo/connectionstyle_demo .. redirect-from:: /tutorials/text/annotations +.. redirect-from:: /gallery/text_labels_and_annotations/annotate_transform .. _annotations: diff --git a/galleries/users_explain/toolkits/axisartist.rst b/galleries/users_explain/toolkits/axisartist.rst index 7ff0897f23d8..a5a1b6cbcd78 100644 --- a/galleries/users_explain/toolkits/axisartist.rst +++ b/galleries/users_explain/toolkits/axisartist.rst @@ -131,7 +131,7 @@ few things that mpl_toolkits.axisartist.Axes is different from original Axes from Matplotlib. * Axis elements (axis line(spine), ticks, ticklabel and axis labels) - are drawn by a AxisArtist instance. Unlike Axis, left, right, top + are drawn by an AxisArtist instance. Unlike Axis, left, right, top and bottom axis are drawn by separate artists. And each of them may have different tick location and different tick labels. @@ -569,7 +569,7 @@ See the first example of this page. Current limitations and TODO's ============================== -The code need more refinement. Here is a incomplete list of issues and TODO's +The code need more refinement. Here is an incomplete list of issues and TODO's * No easy way to support a user customized tick location (for curvilinear grid). A new Locator class needs to be created. diff --git a/lib/matplotlib/__init__.py b/lib/matplotlib/__init__.py index 0b1b0e57dc6d..5b3119a04d7c 100644 --- a/lib/matplotlib/__init__.py +++ b/lib/matplotlib/__init__.py @@ -158,7 +158,7 @@ # cbook must import matplotlib only within function # definitions, so it is safe to import from it here. -from . import _api, _version, cbook, _docstring, rcsetup +from . import _api, _version, cbook, rcsetup from matplotlib._api import MatplotlibDeprecationWarning from matplotlib.colors import _color_sequences as color_sequences from matplotlib.rcsetup import cycler # noqa: F401 @@ -658,23 +658,13 @@ def gen_candidates(): "install is broken") -@_docstring.Substitution( - "\n".join(map("- {}".format, sorted(rcsetup._validators, key=str.lower))) -) class RcParams(MutableMapping, dict): """ A dict-like key-value store for config parameters, including validation. - Validating functions are defined and associated with rc parameters in - :mod:`matplotlib.rcsetup`. + This is the data structure behind `matplotlib.rcParams`. - The list of rcParams is: - - %s - - See Also - -------- - :ref:`customizing-with-matplotlibrc-files` + The complete list of rcParams can be found in :doc:`/users/explain/configuration`. """ validate = rcsetup._validators @@ -1502,6 +1492,8 @@ def func(foo, label=None): ... @functools.wraps(func) def inner(ax, *args, data=None, **kwargs): + __tracebackhide__ = True + if data is None: return func( ax, diff --git a/lib/matplotlib/_api/deprecation.py b/lib/matplotlib/_api/deprecation.py index ce346e02e83d..10fa3e31fd5d 100644 --- a/lib/matplotlib/_api/deprecation.py +++ b/lib/matplotlib/_api/deprecation.py @@ -194,6 +194,7 @@ def emit_warning(): removal=removal) def wrapper(*args, **kwargs): + __tracebackhide__ = True emit_warning() return func(*args, **kwargs) @@ -286,6 +287,8 @@ def func(good_name): ... @functools.wraps(func) def wrapper(*args, **kwargs): + __tracebackhide__ = True + if old in kwargs: warn_deprecated( since, message=f"The {old!r} parameter of {func.__name__}() " @@ -376,6 +379,8 @@ def func(used_arg, other_arg, unused, more_args): ... @functools.wraps(func) def wrapper(*inner_args, **inner_kwargs): + __tracebackhide__ = True + if len(inner_args) <= name_idx and name not in inner_kwargs: # Early return in the simple, non-deprecated case (much faster than # calling bind()). @@ -458,6 +463,8 @@ def violinplot(self, dataset, positions=None, *, vert=None, ...) @functools.wraps(func) def wrapper(*args, **kwargs): + __tracebackhide__ = True + # Don't use signature.bind here, as it would fail when stacked with # rename_parameter and an "old" argument name is passed in # (signature.bind would fail, but the actual call would succeed). diff --git a/lib/matplotlib/_cm_bivar.py b/lib/matplotlib/_cm_bivar.py index 53c0d48d7d6c..688e243accda 100644 --- a/lib/matplotlib/_cm_bivar.py +++ b/lib/matplotlib/_cm_bivar.py @@ -1,9 +1,8 @@ -# auto-generated by https://github.com/trygvrad/multivariate_colormaps -# date: 2024-05-24 - import numpy as np from matplotlib.colors import SegmentedBivarColormap +# auto-generated by https://github.com/trygvrad/multivariate_colormaps +# date: 2024-05-24 BiPeak = np.array( [0.000, 0.674, 0.931, 0.000, 0.680, 0.922, 0.000, 0.685, 0.914, 0.000, 0.691, 0.906, 0.000, 0.696, 0.898, 0.000, 0.701, 0.890, 0.000, 0.706, @@ -1276,32 +1275,9 @@ ]).reshape((65, 65, 3)) BiOrangeBlue = np.array( - [0.000, 0.000, 0.000, 0.000, 0.062, 0.125, 0.000, 0.125, 0.250, 0.000, - 0.188, 0.375, 0.000, 0.250, 0.500, 0.000, 0.312, 0.625, 0.000, 0.375, - 0.750, 0.000, 0.438, 0.875, 0.000, 0.500, 1.000, 0.125, 0.062, 0.000, - 0.125, 0.125, 0.125, 0.125, 0.188, 0.250, 0.125, 0.250, 0.375, 0.125, - 0.312, 0.500, 0.125, 0.375, 0.625, 0.125, 0.438, 0.750, 0.125, 0.500, - 0.875, 0.125, 0.562, 1.000, 0.250, 0.125, 0.000, 0.250, 0.188, 0.125, - 0.250, 0.250, 0.250, 0.250, 0.312, 0.375, 0.250, 0.375, 0.500, 0.250, - 0.438, 0.625, 0.250, 0.500, 0.750, 0.250, 0.562, 0.875, 0.250, 0.625, - 1.000, 0.375, 0.188, 0.000, 0.375, 0.250, 0.125, 0.375, 0.312, 0.250, - 0.375, 0.375, 0.375, 0.375, 0.438, 0.500, 0.375, 0.500, 0.625, 0.375, - 0.562, 0.750, 0.375, 0.625, 0.875, 0.375, 0.688, 1.000, 0.500, 0.250, - 0.000, 0.500, 0.312, 0.125, 0.500, 0.375, 0.250, 0.500, 0.438, 0.375, - 0.500, 0.500, 0.500, 0.500, 0.562, 0.625, 0.500, 0.625, 0.750, 0.500, - 0.688, 0.875, 0.500, 0.750, 1.000, 0.625, 0.312, 0.000, 0.625, 0.375, - 0.125, 0.625, 0.438, 0.250, 0.625, 0.500, 0.375, 0.625, 0.562, 0.500, - 0.625, 0.625, 0.625, 0.625, 0.688, 0.750, 0.625, 0.750, 0.875, 0.625, - 0.812, 1.000, 0.750, 0.375, 0.000, 0.750, 0.438, 0.125, 0.750, 0.500, - 0.250, 0.750, 0.562, 0.375, 0.750, 0.625, 0.500, 0.750, 0.688, 0.625, - 0.750, 0.750, 0.750, 0.750, 0.812, 0.875, 0.750, 0.875, 1.000, 0.875, - 0.438, 0.000, 0.875, 0.500, 0.125, 0.875, 0.562, 0.250, 0.875, 0.625, - 0.375, 0.875, 0.688, 0.500, 0.875, 0.750, 0.625, 0.875, 0.812, 0.750, - 0.875, 0.875, 0.875, 0.875, 0.938, 1.000, 1.000, 0.500, 0.000, 1.000, - 0.562, 0.125, 1.000, 0.625, 0.250, 1.000, 0.688, 0.375, 1.000, 0.750, - 0.500, 1.000, 0.812, 0.625, 1.000, 0.875, 0.750, 1.000, 0.938, 0.875, - 1.000, 1.000, 1.000, - ]).reshape((9, 9, 3)) + [0.0, 0.0, 0.0, 0.0, 0.5, 1.0, + 1.0, 0.5, 0.0, 1.0, 1.0, 1.0, + ]).reshape((2, 2, 3)) cmaps = { "BiPeak": SegmentedBivarColormap( diff --git a/lib/matplotlib/animation.py b/lib/matplotlib/animation.py index 1a398c91dbef..be69dce4e927 100644 --- a/lib/matplotlib/animation.py +++ b/lib/matplotlib/animation.py @@ -955,9 +955,21 @@ def save(self, filename, writer=None, fps=None, dpi=None, codec=None, filename : str The output filename, e.g., :file:`mymovie.mp4`. - writer : `MovieWriter` or str, default: :rc:`animation.writer` - A `MovieWriter` instance to use or a key that identifies a - class to use, such as 'ffmpeg'. + writer : `AbstractMovieWriter` subclass or str, default: :rc:`animation.writer` + The writer used to grab the frames and create the movie file. + This can be an instance of an `AbstractMovieWriter` subclass or a + string. The builtin writers are + + ================== ============================== + str class + ================== ============================== + 'ffmpeg' `.FFMpegWriter` + 'ffmpeg_file' `.FFMpegFileWriter` + 'imagemagick' `.ImageMagickWriter` + 'imagemagick_file' `.ImageMagickFileWriter` + 'pillow' `.PillowWriter` + 'html' `.HTMLWriter` + ================== ============================== fps : int, optional Movie frame rate (per second). If not set, the frame rate from the diff --git a/lib/matplotlib/artist.py b/lib/matplotlib/artist.py index eaaae43e283a..79a629e9f4c6 100644 --- a/lib/matplotlib/artist.py +++ b/lib/matplotlib/artist.py @@ -165,10 +165,24 @@ def _update_set_signature_and_docstring(cls): if prop not in Artist._PROPERTIES_EXCLUDED_FROM_SET]]) cls.set._autogenerated_signature = True - cls.set.__doc__ = ( - "Set multiple properties at once.\n\n" - "Supported properties are\n\n" - + kwdoc(cls)) + cls.set.__doc__ = ("""\ +Set multiple properties at once. + +:: + a.set(a=A, b=B, c=C) + +is equivalent to :: + + a.set_a(A) + a.set_b(B) + a.set_c(C) + +The order of operations is not guaranteed, however most properties do not +depend on each other. + +Supported properties are + +""" + kwdoc(cls)) def __init__(self): self._stale = True diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 470e096eb033..2bdb6ffd6a3f 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -4325,10 +4325,12 @@ def boxplot(self, x, notch=None, sym=None, vert=None, Parameters ---------- - x : Array or a sequence of vectors. - The input data. If a 2D array, a boxplot is drawn for each column - in *x*. If a sequence of 1D arrays, a boxplot is drawn for each - array in *x*. + x : 1D array or sequence of 1D arrays or 2D array + The input data. Possible values: + + - 1D array: A single box is drawn. + - sequence of 1D arrays: A box is drawn for each array in the sequence. + - 2D array: A box is drawn for each column in the array. notch : bool, default: :rc:`boxplot.notch` Whether to draw a notched boxplot (`True`), or a rectangular @@ -5662,8 +5664,8 @@ def reduce_C_function(C: array) -> float ymin, ymax = (ty.min(), ty.max()) if len(y) else (0, 1) # to avoid issues with singular data, expand the min/max pairs - xmin, xmax = mtransforms.nonsingular(xmin, xmax, expander=0.1) - ymin, ymax = mtransforms.nonsingular(ymin, ymax, expander=0.1) + xmin, xmax = mtransforms._nonsingular(xmin, xmax, expander=0.1) + ymin, ymax = mtransforms._nonsingular(ymin, ymax, expander=0.1) nx1 = nx + 1 ny1 = ny + 1 @@ -6805,9 +6807,12 @@ def pcolormesh(self, *args, alpha=None, norm=None, cmap=None, vmin=None, See :doc:`/gallery/images_contours_and_fields/pcolormesh_grids` for more description. - snap : bool, default: False + snap : bool, default: :rc:`pcolormesh.snap` Whether to snap the mesh to pixel boundaries. + .. versionchanged:: 3.4.0 + The default value changed from *False* to *True* to improve transparency + handling. See :ref:`whats-new-3-4-0` for details. rasterized : bool, optional Rasterize the pcolormesh when drawing vector graphics. This can speed up rendering and produce smaller files for large data sets. @@ -7326,6 +7331,9 @@ def hist(self, x, bins=None, range=None, density=False, weights=None, Color or sequence of colors, one per dataset. Default (``None``) uses the standard line color sequence. + .. versionadded:: 3.10 + It is now possible to use a single color with multiple datasets. + label : str or list of str, optional String, or sequence of strings to match multiple datasets. Bar charts yield multiple patches per dataset, but only the first gets @@ -7447,6 +7455,8 @@ def hist(self, x, bins=None, range=None, density=False, weights=None, if color is None: colors = [self._get_lines.get_next_color() for i in range(nx)] else: + if mcolors.is_color_like(color): + color = [color]*nx colors = mcolors.to_rgba_array(color) if len(colors) != nx: raise ValueError(f"The 'color' keyword argument must have one " @@ -8854,8 +8864,12 @@ def violinplot(self, dataset, positions=None, vert=None, Parameters ---------- - dataset : Array or a sequence of vectors. - The input data. + dataset : 1D array or sequence of 1D arrays or 2D array + The input data. Possible values: + + - 1D array: A single violin is drawn. + - sequence of 1D arrays: A violin is drawn for each array in the sequence. + - 2D array: A violin is drawn for each column in the array. positions : array-like, default: [1, 2, ..., n] The positions of the violins; i.e. coordinates on the x-axis for diff --git a/lib/matplotlib/axis.py b/lib/matplotlib/axis.py index c3b6fcac569f..900682511713 100644 --- a/lib/matplotlib/axis.py +++ b/lib/matplotlib/axis.py @@ -140,17 +140,20 @@ def __init__( f"grid.{major_minor}.linewidth", "grid.linewidth", ) - if grid_alpha is None and not mcolors._has_alpha_channel(grid_color): - # alpha precedence: kwarg > color alpha > rcParams['grid.alpha'] - # Note: only resolve to rcParams if the color does not have alpha - # otherwise `grid(color=(1, 1, 1, 0.5))` would work like - # grid(color=(1, 1, 1, 0.5), alpha=rcParams['grid.alpha']) - # so the that the rcParams default would override color alpha. - grid_alpha = mpl._val_or_rc( - # grid_alpha is None so we can use the first key - mpl.rcParams[f"grid.{major_minor}.alpha"], - "grid.alpha", - ) + if grid_alpha is None: + if mcolors._has_alpha_channel(grid_color): + # Extract alpha from the color + # alpha precedence: kwarg > color alpha > rcParams['grid.alpha'] + rgba = mcolors.to_rgba(grid_color) + grid_color = rgba[:3] # RGB only + grid_alpha = rgba[3] # Alpha from color + else: + # No alpha in color, use rcParams + grid_alpha = mpl._val_or_rc( + # grid_alpha is None so we can use the first key + mpl.rcParams[f"grid.{major_minor}.alpha"], + "grid.alpha", + ) grid_kw = {k[5:]: v for k, v in kwargs.items() if k != "rotation_mode"} @@ -348,6 +351,15 @@ def _apply_params(self, **kwargs): grid_kw = {k[5:]: v for k, v in kwargs.items() if k in _gridline_param_names} + # If grid_color has an alpha channel and grid_alpha is not explicitly + # set, extract the alpha from the color. + if 'color' in grid_kw and 'alpha' not in grid_kw: + grid_color = grid_kw['color'] + if mcolors._has_alpha_channel(grid_color): + # Convert to rgba to extract alpha + rgba = mcolors.to_rgba(grid_color) + grid_kw['color'] = rgba[:3] # RGB only + grid_kw['alpha'] = rgba[3] # Alpha channel self.gridline.set(**grid_kw) def update_position(self, loc): diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index e7edb0e7448f..7f6c01089d0d 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -1740,6 +1740,10 @@ class FigureCanvasBase: filetypes = _default_filetypes + # global counter to assign unique ids to blit backgrounds + # see _get_blit_background_id() + _last_blit_background_id = 0 + @_api.classproperty def supports_blit(cls): """If this Canvas sub-class supports blitting.""" @@ -1765,6 +1769,7 @@ def __init__(self, figure=None): # We don't want to scale up the figure DPI more than once. figure._original_dpi = getattr(figure, '_original_dpi', figure.dpi) self._device_pixel_ratio = 1 + self._blit_backgrounds = {} super().__init__() # Typically the GUI widget init (if any). callbacks = property(lambda self: self.figure._canvas_callbacks) @@ -1840,6 +1845,51 @@ def is_saving(self): def blit(self, bbox=None): """Blit the canvas in bbox (default entire canvas).""" + @classmethod + def _get_blit_background_id(cls): + """ + Get a globally unique id that can be used to store a blit background. + + Blitting support is canvas-dependent, so blitting mechanisms should + store their backgrounds in the canvas, more precisely in + ``canvas._blit_backgrounds[id]``. The id must be obtained via this + function to ensure it is globally unique. + + The content of ``canvas._blit_backgrounds[id]`` is not specified. + We leave this freedom to the blitting mechanism. + + Blitting mechanisms must not expect that a background that they + have stored is still there at a later time. The canvas may have + been switched out, or we may add other mechanisms later that + invalidate blit backgrounds (e.g. dpi changes). + Therefore, always query as `_blit_backgrounds.get(id)` and be + prepared for a None return value. + + Note: The blit background API is still experimental and may change + in the future without warning. + """ + cls._last_blit_background_id += 1 + return cls._last_blit_background_id + + def _release_blit_background_id(self, bb_id): + """ + Release a blit background id that is no longer needed. + + This removes the respective entry from the internal storage, i.e. + the ``canvas._blit_backgrounds`` dict, and thus allows to free the + associated memory. + + After releasing the id you must not use it anymore. + + It is safe to release an id that has not been used with the canvas + or that has already been released. + + Note: The blit background API is still experimental and may change + in the future without warning. + """ + if bb_id in self._blit_backgrounds: + del self._blit_backgrounds[bb_id] + def inaxes(self, xy): """ Return the topmost visible `~.axes.Axes` containing the point *xy*. diff --git a/lib/matplotlib/backends/backend_pdf.py b/lib/matplotlib/backends/backend_pdf.py index d63808eb3925..4d64eaccb358 100644 --- a/lib/matplotlib/backends/backend_pdf.py +++ b/lib/matplotlib/backends/backend_pdf.py @@ -2632,10 +2632,7 @@ def delta(self, other): different = ours is not theirs else: different = bool(ours != theirs) - except (ValueError, DeprecationWarning): - # numpy version < 1.25 raises DeprecationWarning when array shapes - # mismatch, unlike numpy >= 1.25 which raises ValueError. - # This should be removed when numpy < 1.25 is no longer supported. + except ValueError: ours = np.asarray(ours) theirs = np.asarray(theirs) different = (ours.shape != theirs.shape or diff --git a/lib/matplotlib/backends/backend_svg.py b/lib/matplotlib/backends/backend_svg.py index 0cb6430ec823..c1f824007a56 100644 --- a/lib/matplotlib/backends/backend_svg.py +++ b/lib/matplotlib/backends/backend_svg.py @@ -1134,7 +1134,8 @@ def _draw_text_as_text(self, gc, x, y, s, prop, angle, ismath, mtext=None): font_style['font-style'] = prop.get_style() if prop.get_variant() != 'normal': font_style['font-variant'] = prop.get_variant() - weight = fm.weight_dict[prop.get_weight()] + weight = prop.get_weight() + weight = fm.weight_dict.get(weight, weight) # convert to int if weight != 400: font_style['font-weight'] = f'{weight}' diff --git a/lib/matplotlib/backends/qt_editor/figureoptions.py b/lib/matplotlib/backends/qt_editor/figureoptions.py index 9c57b7c4e968..cd4e9583cce5 100644 --- a/lib/matplotlib/backends/qt_editor/figureoptions.py +++ b/lib/matplotlib/backends/qt_editor/figureoptions.py @@ -92,7 +92,7 @@ def prepare_data(d, init): `"None"`, `"none"` and `""` are synonyms); *init* is one shorthand of the initial style. - This function returns an list suitable for initializing a + This function returns a list suitable for initializing a FormLayout combobox, namely `[initial_name, (shorthand, style_name), (shorthand, style_name), ...]`. """ diff --git a/lib/matplotlib/cbook.py b/lib/matplotlib/cbook.py index a2a9e54792d9..2e416486baf4 100644 --- a/lib/matplotlib/cbook.py +++ b/lib/matplotlib/cbook.py @@ -1463,9 +1463,13 @@ def violin_stats(X, method=("GaussianKDE", "scott"), points=100, quantiles=None) Parameters ---------- - X : array-like + X : 1D array or sequence of 1D arrays or 2D array Sample data that will be used to produce the gaussian kernel density - estimates. Must have 2 or fewer dimensions. + estimates. Possible values: + + - 1D array: Statistics are computed for that array. + - sequence of 1D arrays: Statistics are computed for each array in the sequence. + - 2D array: Statistics are computed for each column in the array. method : (name, bw_method) or callable, The method used to calculate the kernel density estimate for each diff --git a/lib/matplotlib/collections.py b/lib/matplotlib/collections.py index 684e15cdf854..ceae9fc308a0 100644 --- a/lib/matplotlib/collections.py +++ b/lib/matplotlib/collections.py @@ -670,27 +670,43 @@ def set_linestyle(self, ls): """ Set the linestyle(s) for the collection. - =========================== ================= - linestyle description - =========================== ================= - ``'-'`` or ``'solid'`` solid line - ``'--'`` or ``'dashed'`` dashed line - ``'-.'`` or ``'dashdot'`` dash-dotted line - ``':'`` or ``'dotted'`` dotted line - =========================== ================= + Parameters + ---------- + ls : {'-', '--', '-.', ':', '', ...} or (offset, on-off-seq) or list thereof + If a list, the individual elements are assigned to the elements of the + collection. - Alternatively a dash tuple of the following form can be provided:: + Possible values: - (offset, onoffseq), + - A string: - where ``onoffseq`` is an even length tuple of on and off ink in points. + ======================================================= ================ + linestyle description + ======================================================= ================ + ``'-'`` or ``'solid'`` solid line + ``'--'`` or ``'dashed'`` dashed line + ``'-.'`` or ``'dashdot'`` dash-dotted line + ``':'`` or ``'dotted'`` dotted line + ``''`` or ``'none'`` (discouraged: ``'None'``, ``' '``) draw nothing + ======================================================= ================ - Parameters - ---------- - ls : str or tuple or list thereof - Valid values for individual linestyles include {'-', '--', '-.', - ':', '', (offset, on-off-seq)}. See `.Line2D.set_linestyle` for a - complete description. + - A tuple describing the start position and lengths of dashes and spaces: + + (offset, onoffseq) + + where + + - *offset* is a float specifying the offset (in points); i.e. how much + is the dash pattern shifted. + - *onoffseq* is a sequence of on and off ink in points. There can be + arbitrary many pairs of on and off values. + + Example: The tuple ``(0, (10, 5, 1, 5))`` means that the pattern starts + at the beginning of the line. It draws a 10 point long dash, + then a 5 point long space, then a 1 point long dash, followed by a 5 point + long space, and then the pattern repeats. + + For examples see :doc:`/gallery/lines_bars_and_markers/linestyles`. """ # get the list of raw 'unscaled' dash patterns self._us_linestyles = mlines._get_dash_patterns(ls) diff --git a/lib/matplotlib/colorbar.py b/lib/matplotlib/colorbar.py index 2a11477ed1c2..a4292a323035 100644 --- a/lib/matplotlib/colorbar.py +++ b/lib/matplotlib/colorbar.py @@ -1098,7 +1098,7 @@ def _process_values(self): # If we still aren't scaled after autoscaling, use 0, 1 as default self.norm.vmin = 0 self.norm.vmax = 1 - self.norm.vmin, self.norm.vmax = mtransforms.nonsingular( + self.norm.vmin, self.norm.vmax = mtransforms._nonsingular( self.norm.vmin, self.norm.vmax, expander=0.1) if (not isinstance(self.norm, colors.BoundaryNorm) and (self.boundaries is None)): diff --git a/lib/matplotlib/colors.py b/lib/matplotlib/colors.py index 47e6cd1a2b89..c5dd5d3b13fe 100644 --- a/lib/matplotlib/colors.py +++ b/lib/matplotlib/colors.py @@ -55,7 +55,7 @@ import matplotlib as mpl import numpy as np -from matplotlib import _api, _cm, cbook, scale, _image +from matplotlib import _api, _cm, cbook, scale from ._color_data import BASE_COLORS, TABLEAU_COLORS, CSS4_COLORS, XKCD_COLORS @@ -2212,16 +2212,37 @@ def __init__(self, patch, N=256, shape='square', origin=(0, 0), super().__init__(N, N, shape, origin, name=name) def _init(self): + # Perform bilinear interpolation + s = self.patch.shape - _patch = np.empty((s[0], s[1], 4)) - _patch[:, :, :3] = self.patch - _patch[:, :, 3] = 1 - transform = mpl.transforms.Affine2D().translate(-0.5, -0.5)\ - .scale(self.N / (s[1] - 1), self.N / (s[0] - 1)) - self._lut = np.empty((self.N, self.N, 4)) - - _image.resample(_patch, self._lut, transform, _image.BILINEAR, - resample=False, alpha=1) + + # Indices (whole and fraction) of the new grid points + row = np.linspace(0, s[0] - 1, self.N)[:, np.newaxis] + col = np.linspace(0, s[1] - 1, self.N)[np.newaxis, :] + left = row.astype(int) # floor not needed because all values are nonnegative + top = col.astype(int) # floor not needed because all values are nonnegative + row_frac = (row - left)[:, :, np.newaxis] + col_frac = (col - top)[:, :, np.newaxis] + + # Indices of the next edges, clipping where needed + right = np.clip(left + 1, 0, s[0] - 1) + bottom = np.clip(top + 1, 0, s[1] - 1) + + # Values at the corners + tl = self.patch[left, top, :] + tr = self.patch[right, top, :] + bl = self.patch[left, bottom, :] + br = self.patch[right, bottom, :] + + # Interpolate between the corners + lut = (tl * (1 - row_frac) * (1 - col_frac) + + tr * row_frac * (1 - col_frac) + + bl * (1 - row_frac) * col_frac + + br * row_frac * col_frac) + + # Add the alpha channel + self._lut = np.concatenate([lut, np.ones((self.N, self.N, 1))], axis=2) + self._isinit = True diff --git a/lib/matplotlib/dates.py b/lib/matplotlib/dates.py index 511e1c6df6cc..6a2bd0194bd4 100644 --- a/lib/matplotlib/dates.py +++ b/lib/matplotlib/dates.py @@ -537,7 +537,7 @@ def drange(dstart, dend, delta): # calculate end of the interval which will be generated dinterval_end = dstart + num * delta - # ensure, that an half open interval will be generated [dstart, dend) + # ensure, that a half open interval will be generated [dstart, dend) if dinterval_end >= dend: # if the endpoint is greater than or equal to dend, # just subtract one delta diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py index 4cd7fd01a995..85b378fcd422 100644 --- a/lib/matplotlib/figure.py +++ b/lib/matplotlib/figure.py @@ -2766,7 +2766,7 @@ def show(self, warn=True): .. warning:: - This does not manage an GUI event loop. Consequently, the figure + This does not manage a GUI event loop. Consequently, the figure may only be shown briefly or not shown at all if you or your environment are not managing an event loop. diff --git a/lib/matplotlib/font_manager.py b/lib/matplotlib/font_manager.py index ab6b495631de..98361eaa01e8 100644 --- a/lib/matplotlib/font_manager.py +++ b/lib/matplotlib/font_manager.py @@ -748,7 +748,7 @@ def get_variant(self): def get_weight(self): """ - Set the font weight. Options are: A numeric value in the + Get the font weight. Options are: A numeric value in the range 0-1000 or one of 'light', 'normal', 'regular', 'book', 'medium', 'roman', 'semibold', 'demibold', 'demi', 'bold', 'heavy', 'extra bold', 'black' diff --git a/lib/matplotlib/inset.py b/lib/matplotlib/inset.py index fb5bfacff924..aae640db6f81 100644 --- a/lib/matplotlib/inset.py +++ b/lib/matplotlib/inset.py @@ -126,26 +126,40 @@ def set_linestyle(self, ls): """ Set the linestyle of the rectangle and the connectors. - ======================================================= ================ - linestyle description - ======================================================= ================ - ``'-'`` or ``'solid'`` solid line - ``'--'`` or ``'dashed'`` dashed line - ``'-.'`` or ``'dashdot'`` dash-dotted line - ``':'`` or ``'dotted'`` dotted line - ``''`` or ``'none'`` (discouraged: ``'None'``, ``' '``) draw nothing - ======================================================= ================ + Parameters + ---------- + ls : {'-', '--', '-.', ':', '', ...} or (offset, on-off-seq) + Possible values: - Alternatively a dash tuple of the following form can be provided:: + - A string: - (offset, onoffseq) + ======================================================= ================ + linestyle description + ======================================================= ================ + ``'-'`` or ``'solid'`` solid line + ``'--'`` or ``'dashed'`` dashed line + ``'-.'`` or ``'dashdot'`` dash-dotted line + ``':'`` or ``'dotted'`` dotted line + ``''`` or ``'none'`` (discouraged: ``'None'``, ``' '``) draw nothing + ======================================================= ================ - where ``onoffseq`` is an even length tuple of on and off ink in points. + - A tuple describing the start position and lengths of dashes and spaces: - Parameters - ---------- - ls : {'-', '--', '-.', ':', '', (offset, on-off-seq), ...} - The line style. + (offset, onoffseq) + + where + + - *offset* is a float specifying the offset (in points); i.e. how much + is the dash pattern shifted. + - *onoffseq* is a sequence of on and off ink in points. There can be + arbitrary many pairs of on and off values. + + Example: The tuple ``(0, (10, 5, 1, 5))`` means that the pattern starts + at the beginning of the line. It draws a 10 point long dash, + then a 5 point long space, then a 1 point long dash, followed by a 5 point + long space, and then the pattern repeats. + + For examples see :doc:`/gallery/lines_bars_and_markers/linestyles`. """ self._shared_setter('linestyle', ls) diff --git a/lib/matplotlib/lines.py b/lib/matplotlib/lines.py index 72c57bf77b5c..7c374843b5c1 100644 --- a/lib/matplotlib/lines.py +++ b/lib/matplotlib/lines.py @@ -183,7 +183,7 @@ def _slice_or_none(in_v, slc): if ax is None: raise ValueError( "markevery is specified relative to the Axes size, but " - "the line does not have a Axes as parent") + "the line does not have an Axes as parent") # calc cumulative distance along path (in display coords): fin = np.isfinite(verts).all(axis=1) @@ -1149,7 +1149,7 @@ def set_linestyle(self, ls): Parameters ---------- - ls : {'-', '--', '-.', ':', '', (offset, on-off-seq), ...} + ls : {'-', '--', '-.', ':', '', ...} or (offset, on-off-seq) Possible values: - A string: @@ -1164,13 +1164,23 @@ def set_linestyle(self, ls): ``''`` or ``'none'`` (discouraged: ``'None'``, ``' '``) draw nothing ======================================================= ================ - - Alternatively a dash tuple of the following form can be - provided:: + - A tuple describing the start position and lengths of dashes and spaces: (offset, onoffseq) - where ``onoffseq`` is an even length tuple of on and off ink - in points. See also :meth:`set_dashes`. + where + + - *offset* is a float specifying the offset (in points); i.e. how much + is the dash pattern shifted. + - *onoffseq* is a sequence of on and off ink in points. There can be + arbitrary many pairs of on and off values. + + Example: The tuple ``(0, (10, 5, 1, 5))`` means that the pattern starts + at the beginning of the line. It draws a 10 point long dash, + then a 5 point long space, then a 1 point long dash, followed by a 5 point + long space, and then the pattern repeats. + + See also :meth:`set_dashes`. For examples see :doc:`/gallery/lines_bars_and_markers/linestyles`. """ diff --git a/lib/matplotlib/markers.py b/lib/matplotlib/markers.py index 9c6b3da73bcd..52fa0797536a 100644 --- a/lib/matplotlib/markers.py +++ b/lib/matplotlib/markers.py @@ -129,7 +129,7 @@ """ import copy -from collections.abc import Sized +from collections.abc import Hashable, Sized import numpy as np @@ -308,7 +308,7 @@ def _set_marker(self, marker): """ if isinstance(marker, str) and cbook.is_math_text(marker): self._marker_function = self._set_mathtext_path - elif isinstance(marker, (int, str)) and marker in self.markers: + elif isinstance(marker, Hashable) and marker in self.markers: self._marker_function = getattr(self, '_set_' + self.markers[marker]) elif (isinstance(marker, np.ndarray) and marker.ndim == 2 and marker.shape[1] == 2): diff --git a/lib/matplotlib/patches.py b/lib/matplotlib/patches.py index 45def2df17b8..0c8d6b5fee15 100644 --- a/lib/matplotlib/patches.py +++ b/lib/matplotlib/patches.py @@ -468,26 +468,40 @@ def set_linestyle(self, ls): """ Set the patch linestyle. - ======================================================= ================ - linestyle description - ======================================================= ================ - ``'-'`` or ``'solid'`` solid line - ``'--'`` or ``'dashed'`` dashed line - ``'-.'`` or ``'dashdot'`` dash-dotted line - ``':'`` or ``'dotted'`` dotted line - ``''`` or ``'none'`` (discouraged: ``'None'``, ``' '``) draw nothing - ======================================================= ================ + Parameters + ---------- + ls : {'-', '--', '-.', ':', '', ...} or (offset, on-off-seq) + Possible values: - Alternatively a dash tuple of the following form can be provided:: + - A string: - (offset, onoffseq) + ======================================================= ================ + linestyle description + ======================================================= ================ + ``'-'`` or ``'solid'`` solid line + ``'--'`` or ``'dashed'`` dashed line + ``'-.'`` or ``'dashdot'`` dash-dotted line + ``':'`` or ``'dotted'`` dotted line + ``''`` or ``'none'`` (discouraged: ``'None'``, ``' '``) draw nothing + ======================================================= ================ - where ``onoffseq`` is an even length tuple of on and off ink in points. + - A tuple describing the start position and lengths of dashes and spaces: - Parameters - ---------- - ls : {'-', '--', '-.', ':', '', (offset, on-off-seq), ...} - The line style. + (offset, onoffseq) + + where + + - *offset* is a float specifying the offset (in points); i.e. how much + is the dash pattern shifted. + - *onoffseq* is a sequence of on and off ink in points. There can be + arbitrary many pairs of on and off values. + + Example: The tuple ``(0, (10, 5, 1, 5))`` means that the pattern starts + at the beginning of the line. It draws a 10 point long dash, + then a 5 point long space, then a 1 point long dash, followed by a 5 point + long space, and then the pattern repeats. + + For examples see :doc:`/gallery/lines_bars_and_markers/linestyles`. """ if ls is None: ls = "solid" diff --git a/lib/matplotlib/projections/polar.py b/lib/matplotlib/projections/polar.py index 8b0a01f556e3..75e1295f77f1 100644 --- a/lib/matplotlib/projections/polar.py +++ b/lib/matplotlib/projections/polar.py @@ -469,7 +469,7 @@ def view_limits(self, vmin, vmax): if self._zero_in_bounds() and vmax > vmin: # this allows inverted r/y-lims vmin = min(0, vmin) - return mtransforms.nonsingular(vmin, vmax) + return mtransforms._nonsingular(vmin, vmax) class _ThetaShift(mtransforms.ScaledTranslation): diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index 225684d068f4..61f5f48f1224 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -1842,7 +1842,7 @@ def subplots( axs[0, 0].plot(x, y) axs[1, 1].scatter(x, y) - # Share a X axis with each column of subplots + # Share an X axis with each column of subplots plt.subplots(2, 2, sharex='col') # Share a Y axis with each row of subplots diff --git a/lib/matplotlib/rcsetup.py b/lib/matplotlib/rcsetup.py index a088274b3439..5cd42750d27f 100644 --- a/lib/matplotlib/rcsetup.py +++ b/lib/matplotlib/rcsetup.py @@ -13,12 +13,17 @@ root source directory. """ + import ast +from dataclasses import dataclass from functools import lru_cache, reduce from numbers import Real import operator import os +from pathlib import Path import re +from typing import Any +from collections.abc import Callable import numpy as np @@ -67,6 +72,36 @@ def __call__(self, s): msg += "; remove quotes surrounding your string" raise ValueError(msg) + def __repr__(self): + return (f"{self.__class__.__name__}(" + f"key={self.key!r}, valid={[*self.valid.values()]}, " + f"ignorecase={self.ignorecase})") + + def __eq__(self, other): + if self is other: + return True + if not isinstance(other, ValidateInStrings): + return NotImplemented + return ( + self.key, + self.ignorecase, + self._deprecated_since, + tuple(sorted(self.valid.items())) + ) == ( + other.key, + other.ignorecase, + other._deprecated_since, + tuple(sorted(other.valid.items())) + ) + + def __hash__(self): + return hash(( + self.key, + self.ignorecase, + self._deprecated_since, + tuple(sorted(self.valid.items())) + )) + def _single_string_color_list(s, scalar_validator): """ @@ -215,10 +250,11 @@ def validator(s): validate_string, doc='return a list of strings') validate_int = _make_type_validator(int) validate_int_or_None = _make_type_validator(int, allow_none=True) +validate_intlist = _listify_validator(validate_int, n=2) validate_float = _make_type_validator(float) validate_float_or_None = _make_type_validator(float, allow_none=True) validate_floatlist = _listify_validator( - validate_float, doc='return a list of floats') + validate_float) def _validate_marker(s): @@ -1112,7 +1148,7 @@ def _convert_validator_spec(key, conv): "axes.labelcolor": validate_color, # color of axis label # use scientific notation if log10 of the axis range is smaller than the # first or larger than the second - "axes.formatter.limits": _listify_validator(validate_int, n=2), + "axes.formatter.limits": validate_intlist, # use current locale to format ticks "axes.formatter.use_locale": validate_bool, "axes.formatter.use_mathtext": validate_bool, @@ -1405,3 +1441,1855 @@ def _convert_validator_spec(key, conv): } _validators = {k: _convert_validator_spec(k, conv) for k, conv in _validators.items()} + + +@dataclass +class _Param: + name: str + default: Any + validator: Callable[[Any], Any] + description: str = None + + +_params = [ + _Param( + "webagg.port", + default=8988, + validator=validate_int, + description="The port to use for the web server in the WebAgg backend." + ), + _Param( + "webagg.address", + default="127.0.0.1", + validator=validate_string, + description="The address on which the WebAgg web server should be reachable." + ), + _Param( + "webagg.port_retries", + default=50, + validator=validate_int, + description="If webagg.port is unavailable, a number of other random ports " + "will be tried until one that is available is found." + ), + _Param( + "webagg.open_in_browser", + default=True, + validator=validate_bool, + description="When True, open the web browser to the plot that is shown" + ), + _Param( + "backend_fallback", + default=True, + validator=validate_bool, + description="If you are running pyplot inside a GUI and your backend choice " + "conflicts, we will automatically try to find a compatible one for " + "you if backend_fallback is True" + ), + _Param( + "interactive", + default=False, + validator=validate_bool + ), + _Param( + "figure.hooks", + default=[], + validator=validate_stringlist, + description="list of dotted.module.name:dotted.callable.name" + ), + _Param( + "toolbar", + default="toolbar2", + validator=_validate_toolbar, + description="{None, toolbar2, toolmanager}" + ), + _Param( + "timezone", + default="UTC", + validator=validate_string, + description="a pytz timezone string, e.g., US/Central or Europe/Paris" + ), + _Param( + "lines.linewidth", + default=1.5, + validator=validate_float, + description="line width in points" + ), + _Param( + "lines.linestyle", + default="-", + validator=_validate_linestyle, + description="solid line" + ), + _Param( + "lines.color", + default="C0", + validator=validate_color, + description="has no affect on plot(); see axes.prop_cycle" + ), + _Param( + "lines.marker", + default="None", + validator=_validate_marker, + description="the default marker" + ), + _Param( + "lines.markerfacecolor", + default="auto", + validator=validate_color_or_auto, + description="the default marker face color" + ), + _Param( + "lines.markeredgecolor", + default="auto", + validator=validate_color_or_auto, + description="the default marker edge color" + ), + _Param( + "lines.markeredgewidth", + default=1.0, + validator=validate_float, + description="the line width around the marker symbol" + ), + _Param( + "lines.markersize", + default=6.0, + validator=validate_float, + description="marker size, in points" + ), + _Param( + "lines.dash_joinstyle", + default="round", + validator=JoinStyle, + description="{miter, round, bevel}" + ), + _Param( + "lines.dash_capstyle", + default="butt", + validator=CapStyle, + description="{butt, round, projecting}" + ), + _Param( + "lines.solid_joinstyle", + default="round", + validator=JoinStyle, + description="{miter, round, bevel}" + ), + _Param( + "lines.solid_capstyle", + default="projecting", + validator=CapStyle, + description="{butt, round, projecting}" + ), + _Param( + "lines.antialiased", + default=True, + validator=validate_bool, + description="render lines in antialiased (no jaggies)" + ), + _Param( + "lines.dashed_pattern", + default=[3.7, 1.6], + validator=validate_floatlist, + description="The dash pattern for linestyle 'dashed'" + ), + _Param( + "lines.dashdot_pattern", + default=[6.4, 1.6, 1.0, 1.6], + validator=validate_floatlist, + description="The dash pattern for linestyle 'dashdot'" + ), + _Param( + "lines.dotted_pattern", + default=[1.0, 1.65], + validator=validate_floatlist, + description="The dash pattern for linestyle 'dotted'" + ), + _Param( + "lines.scale_dashes", + default=True, + validator=validate_bool + ), + _Param( + "markers.fillstyle", + default="full", + validator=validate_fillstyle, + description="{full, left, right, bottom, top, none}" + ), + _Param( + "pcolor.shading", + default="auto", + validator=["auto", "flat", "nearest", "gouraud"] + ), + _Param( + "pcolormesh.snap", + default=True, + validator=validate_bool, + description="Whether to snap the mesh to pixel boundaries. This is provided " + "solely to allow old test images to remain unchanged. Set to False " + "to obtain the previous behavior." + ), + _Param( + "patch.linewidth", + default=1.0, + validator=validate_float, + description="edge width in points." + ), + _Param( + "patch.facecolor", + default="C0", + validator=validate_color + ), + _Param( + "patch.edgecolor", + default="black", + validator=validate_color, + description='By default, Patches and Collections do not draw edges. This value ' + 'is only used if facecolor is "none" (an Artist without facecolor ' + 'and edgecolor would be invisible) or if patch.force_edgecolor ' + 'is True.' + ), + _Param( + "patch.force_edgecolor", + default=False, + validator=validate_bool, + description="By default, Patches and Collections do not draw edges. Set this " + "to True to draw edges with patch.edgedcolor as the default " + "edgecolor. This is mainly relevant for styles." + ), + _Param( + "patch.antialiased", + default=True, + validator=validate_bool, + description="render patches in antialiased (no jaggies)" + ), + _Param("hatch.color", "edge", _validate_color_or_edge), + _Param("hatch.linewidth", 1.0, validate_float), + _Param("boxplot.notch", False, validate_bool), + _Param("boxplot.vertical", True, validate_bool), + _Param("boxplot.whiskers", 1.5, validate_whiskers), + _Param("boxplot.bootstrap", None, validate_int_or_None), + _Param("boxplot.patchartist", False, validate_bool), + _Param("boxplot.showmeans", False, validate_bool), + _Param("boxplot.showcaps", True, validate_bool), + _Param("boxplot.showbox", True, validate_bool), + _Param("boxplot.showfliers", True, validate_bool), + _Param("boxplot.meanline", False, validate_bool), + _Param("boxplot.flierprops.color", "black", validate_color), + _Param("boxplot.flierprops.marker", "o", _validate_marker), + _Param("boxplot.flierprops.markerfacecolor", "none", validate_color_or_auto), + _Param("boxplot.flierprops.markeredgecolor", "black", validate_color), + _Param("boxplot.flierprops.markeredgewidth", 1.0, validate_float), + _Param("boxplot.flierprops.markersize", 6.0, validate_float), + _Param("boxplot.flierprops.linestyle", "none", _validate_linestyle), + _Param("boxplot.flierprops.linewidth", 1.0, validate_float), + _Param("boxplot.boxprops.color", "black", validate_color), + _Param("boxplot.boxprops.linewidth", 1.0, validate_float), + _Param("boxplot.boxprops.linestyle", "-", _validate_linestyle), + _Param("boxplot.whiskerprops.color", "black", validate_color), + _Param("boxplot.whiskerprops.linewidth", 1.0, validate_float), + _Param("boxplot.whiskerprops.linestyle", "-", _validate_linestyle), + _Param("boxplot.capprops.color", "black", validate_color), + _Param("boxplot.capprops.linewidth", 1.0, validate_float), + _Param("boxplot.capprops.linestyle", "-", _validate_linestyle), + _Param("boxplot.medianprops.color", "C1", validate_color), + _Param("boxplot.medianprops.linewidth", 1.0, validate_float), + _Param("boxplot.medianprops.linestyle", "-", _validate_linestyle), + _Param("boxplot.meanprops.color", "C2", validate_color), + _Param("boxplot.meanprops.marker", "^", _validate_marker), + _Param("boxplot.meanprops.markerfacecolor", "C2", validate_color), + _Param("boxplot.meanprops.markeredgecolor", "C2", validate_color), + _Param("boxplot.meanprops.markersize", 6.0, validate_float), + _Param("boxplot.meanprops.linestyle", "--", _validate_linestyle), + _Param("boxplot.meanprops.linewidth", 1.0, validate_float), + _Param("font.family", ["sans-serif"], validate_stringlist), + _Param("font.style", "normal", validate_string), + _Param("font.variant", "normal", validate_string), + _Param("font.weight", "normal", validate_fontweight), + _Param("font.stretch", "normal", validate_fontstretch), + _Param("font.size", 10.0, validate_float), + _Param( + "font.serif", + default=[ + "DejaVu Serif", "Bitstream Vera Serif", "Computer Modern Roman", + "New Century Schoolbook", "Century Schoolbook L", "Utopia", "ITC Bookman", + "Bookman", "Nimbus Roman No9 L", "Times New Roman", "Times", "Palatino", + "Charter", "serif", + ], + validator=validate_stringlist + ), + _Param( + "font.sans-serif", + default=[ + "DejaVu Sans", "Bitstream Vera Sans", "Computer Modern Sans Serif", + "Lucida Grande", "Verdana", "Geneva", "Lucid", "Arial", "Helvetica", + "Avant Garde", "sans-serif", + ], + validator=validate_stringlist + ), + _Param( + "font.cursive", + default=[ + "Apple Chancery", "Textile", "Zapf Chancery", "Sand", "Script MT", "Felipa", + "Comic Neue", "Comic Sans MS", "cursive", + ], + validator=validate_stringlist + ), + _Param( + "font.fantasy", + default=["Chicago", "Charcoal", "Impact", "Western", "xkcd script", "fantasy"], + validator=validate_stringlist + ), + _Param( + "font.monospace", + default=[ + "DejaVu Sans Mono", "Bitstream Vera Sans Mono", + "Computer Modern Typewriter", "Andale Mono", "Nimbus Mono L", "Courier New", + "Courier", "Fixed", "Terminal", "monospace", + ], + validator=validate_stringlist + ), + _Param( + "font.enable_last_resort", + default=True, + validator=validate_bool, + description="If True, then Unicode Consortium's Last Resort font will be " + "appended to all font selections. This ensures that there will " + "always be a glyph displayed." + ), + _Param( + "text.color", + default="black", + validator=validate_color + ), + _Param("text.hinting", + default="force_autohint", + validator=[ + "default", "no_autohint", "force_autohint", "no_hinting", "auto", "native", + "either", "none", + ], + description="FreeType hinting flag (\"foo\" corresponds to FT_LOAD_FOO); may " + "be one of the following (Proprietary Matplotlib-specific synonyms " + "are given in parentheses, but their use is discouraged): " + "- default: Use the font's native hinter if possible, else " + " FreeType's auto-hinter. (\"either\" is a synonym)." + "- no_autohint: Use the font's native hinter if possible, else " + " don't hint. (\"native\" is a synonym.)" + "- force_autohint: Use FreeType's auto-hinter. (\"auto\" is a " + " synonym.)" + "- no_hinting: Disable hinting. (\"none\" is a synonym.)" + ), + _Param( + "text.hinting_factor", + default=8, + validator=validate_int, + description="Specifies the amount of softness for hinting in the horizontal " + "direction. A value of 1 will hint to full pixels. A value of 2 " + "will hint to half pixels etc." + ), + _Param( + "text.kerning_factor", + default=0, + validator=validate_int, + description="Specifies the scaling factor for kerning values. This is " + "provided solely to allow old test images to remain unchanged. " + "Set to 6 to obtain previous behavior. Values other than 0 or 6 " + "have no defined meaning." + ), + _Param( + "text.antialiased", + default=True, + validator=validate_bool, + description="If True (default), the text will be antialiased. This only " + "affects raster outputs." + ), + _Param( + "text.parse_math", + default=True, + validator=validate_bool, + description="Use mathtext if there is an even number of unescaped dollar signs." + + ), + _Param( + "text.usetex", + default=False, + validator=validate_bool, + description="use latex for all text handling. The following fonts are " + "supported through the usual rc parameter settings: " + "new century schoolbook, bookman, times, palatino, zapf chancery, " + "charter, serif, sans-serif, helvetica, avant garde, courier, " + "monospace, computer modern roman, computer modern sans serif, " + "computer modern typewriter" + ), + _Param( + "text.latex.preamble", + default="", + validator=validate_string, + description='IMPROPER USE OF THIS FEATURE WILL LEAD TO LATEX FAILURES AND IS ' + 'THEREFORE UNSUPPORTED. PLEASE DO NOT ASK FOR HELP IF THIS FEATURE ' + 'DOES NOT DO WHAT YOU EXPECT IT TO. text.latex.preamble is a ' + 'single line of LaTeX code that will be passed on to the LaTeX ' + 'system. It may contain any code that is valid for the LaTeX ' + '"preamble", i.e. between the "\\documentclass" and ' + '"\\begin{document}" statements. Note that it has to be put on a ' + 'single line, which may become quite long. The following packages ' + 'are always loaded with usetex, so beware of package collisions: ' + ' color, fix-cm, geometry, graphicx, textcomp. PostScript ' + '(PSNFSS) font packages may also be loaded, depending on your font ' + 'settings.' + ), + _Param( + "mathtext.fontset", + default="dejavusans", + validator=["dejavusans", "dejavuserif", "cm", "stix", "stixsans", "custom"], + description="Should be 'dejavusans' (default), 'dejavuserif', " + "'cm' (Computer Modern), 'stix', 'stixsans' or 'custom'" + ), + _Param("mathtext.bf", "sans:bold", validate_font_properties), + _Param("mathtext.bfit", "sans:italic:bold", validate_font_properties), + _Param("mathtext.cal", "cursive", validate_font_properties), + _Param("mathtext.it", "sans:italic", validate_font_properties), + _Param("mathtext.rm", "sans", validate_font_properties), + _Param("mathtext.sf", "sans", validate_font_properties), + _Param("mathtext.tt", "monospace", validate_font_properties), + _Param( + "mathtext.fallback", + default="cm", + validator=_validate_mathtext_fallback, + description="Select fallback font from ['cm' (Computer Modern), 'stix', " + "'stixsans'] when a symbol cannot be found in one of the custom " + "math fonts. Select 'None' to not perform fallback and replace the " + "missing character by a dummy symbol." + ), + _Param("mathtext.default", "it", + ["rm", "cal", "bfit", "it", "tt", "sf", "bf", "default", "bb", "frak", "scr", + "regular", ], + description='The default font to use for math. Can be any of the LaTeX font ' + 'names, including the special name "regular" for the same font ' + 'used in regular text.', + ), + _Param( + "axes.facecolor", + default="white", + validator=validate_color, + description="axes background color" + ), + _Param( + "axes.edgecolor", + default="black", + validator=validate_color, + description="axes edge color" + ), + _Param( + "axes.linewidth", + default=0.8, + validator=validate_float, + description="edge line width" + ), + _Param( + "axes.grid", + default=False, + validator=validate_bool, + description="display grid or not" + ), + _Param( + "axes.grid.axis", + default="both", + validator=["x", "y", "both"], + description="which axis the grid should apply to" + ), + _Param( + "axes.grid.which", + default="major", + validator=["minor", "both", "major"], + description="grid lines at {major, minor, both} ticks" + ), + _Param( + "axes.titlelocation", + default="center", + validator=["left", "center", "right"], + description="alignment of the title: {left, right, center}" + ), + _Param( + "axes.titlesize", + default="large", + validator=validate_fontsize, + description="font size of the axes title" + ), + _Param( + "axes.titleweight", + default="normal", + validator=validate_fontweight, + description="font weight of title" + ), + _Param( + "axes.titlecolor", + default="auto", + validator=validate_color_or_auto, + description="color of the axes title, auto falls back to text.color as default " + "value" + ), + _Param( + "axes.titley", + default=None, + validator=validate_float_or_None, + description="position title (axes relative units). None implies auto" + ), + _Param( + "axes.titlepad", + default=6.0, + validator=validate_float, + description="pad between axes and title in points" + ), + _Param( + "axes.labelsize", + default="medium", + validator=validate_fontsize, + description="font size of the x and y labels" + ), + _Param( + "axes.labelpad", + default=4.0, + validator=validate_float, + description="space between label and axis" + ), + _Param( + "axes.labelweight", + default="normal", + validator=validate_fontweight, + description="weight of the x and y labels" + ), + _Param( + "axes.labelcolor", + default="black", + validator=validate_color + ), + _Param( + "axes.axisbelow", + default="line", + validator=validate_axisbelow, + description="draw axis gridlines and ticks: " + "- below patches (True) " + "- above patches but below lines ('line') " + "- above all (False)" + ), + _Param( + "axes.formatter.limits", + default=[-5, 6], + validator=validate_intlist, + description="use scientific notation if log10 of the axis range is smaller " + "than the first or larger than the second" + ), + _Param( + "axes.formatter.use_locale", + default=False, + validator=validate_bool, + description="When True, format tick labels according to the user's locale. " + "For example, use ',' as a decimal separator in the fr_FR locale." + ), + _Param( + "axes.formatter.use_mathtext", + default=False, + validator=validate_bool, + description="When True, use mathtext for scientific notation." + ), + _Param( + "axes.formatter.min_exponent", + default=0, + validator=validate_int, + description="minimum exponent to format in scientific notation" + ), + _Param( + "axes.formatter.useoffset", + default=True, + validator=validate_bool, + description="If True, the tick label formatter will default to labeling ticks " + "relative to an offset when the data range is small compared to " + "the minimum absolute value of the data." + ), + _Param( + "axes.formatter.offset_threshold", + default=4, + validator=validate_int, + description="When useoffset is True, the offset will be used when it can " + "remove at least this number of significant digits from tick " + "labels." + ), + _Param( + "axes.spines.left", + default=True, + validator=validate_bool, + description="display axis spines" + ), + _Param("axes.spines.bottom", True, validate_bool), + _Param("axes.spines.top", True, validate_bool), + _Param( + "axes.spines.right", + default=True, + validator=validate_bool + ), + _Param( + "axes.unicode_minus", + default=True, + validator=validate_bool, + description="use Unicode for the minus symbol rather than hyphen. See " + "https://en.wikipedia.org/wiki/Plus_and_minus_signs#Character_codes" + + ), + _Param("axes.prop_cycle", + default=cycler( + "color", + [(0.12156862745098039, 0.4666666666666667, 0.7058823529411765), + (1.0, 0.4980392156862745, 0.054901960784313725), + (0.17254901960784313, 0.6274509803921569, 0.17254901960784313), + (0.8392156862745098, 0.15294117647058825, 0.1568627450980392), + (0.5803921568627451, 0.403921568627451, 0.7411764705882353), + (0.5490196078431373, 0.33725490196078434, 0.29411764705882354), + (0.8901960784313725, 0.4666666666666667, 0.7607843137254902), + (0.4980392156862745, 0.4980392156862745, 0.4980392156862745), + (0.7372549019607844, 0.7411764705882353, 0.13333333333333333), + (0.09019607843137255, 0.7450980392156863, 0.8117647058823529), + ], + ), + validator=validate_cycler + ), + _Param( + "axes.xmargin", + default=0.05, + validator=_validate_greaterthan_minushalf, + description="x margin. See `~.axes.Axes.margins`" + ), + _Param( + "axes.ymargin", + default=0.05, + validator=_validate_greaterthan_minushalf, + description="y margin. See `~.axes.Axes.margins`" + ), + _Param( + "axes.zmargin", + default=0.05, + validator=_validate_greaterthan_minushalf, + description="z margin. See `~.axes.Axes.margins`" + ), + _Param( + "axes.autolimit_mode", + default="data", + validator=["data", "round_numbers"], + description='If "data", use axes.xmargin and axes.ymargin as is. If ' + '"round_numbers", after application of margins, axis limits are ' + 'further expanded to the nearest "round" number.', + ), + _Param( + "polaraxes.grid", + default=True, + validator=validate_bool, + description="display grid on polar axes" + ), + _Param( + "axes3d.grid", + default=True, + validator=validate_bool, description="display grid on 3D axes" + ), + _Param( + "axes3d.automargin", + default=False, + validator=validate_bool, + description="automatically add margin when manually setting 3D axis limits" + ), + _Param( + "axes3d.xaxis.panecolor", + default=(0.95, 0.95, 0.95, 0.5), + validator=validate_color, + description="background pane on 3D axes" + ), + _Param( + "axes3d.yaxis.panecolor", + default=(0.9, 0.9, 0.9, 0.5), + validator=validate_color, + description="background pane on 3D axes" + ), + _Param( + "axes3d.zaxis.panecolor", + default=(0.925, 0.925, 0.925, 0.5), + validator=validate_color, + description="background pane on 3D axes" + ), + _Param( + "axes3d.depthshade", + default=True, + validator=validate_bool, + description="depth shade for 3D scatter plots" + ), + _Param( + "axes3d.depthshade_minalpha", + default=0.3, + validator=validate_float, + description="minimum alpha value for depth shading" + ), + _Param( + "axes3d.mouserotationstyle", + default="arcball", + validator=["azel", "trackball", "sphere", "arcball"], + description="{azel, trackball, sphere, arcball} See also " + "https://matplotlib.org/stable/api/toolkits/mplot3d/view_angles.html#rotation-with-mouse"), # noqa + _Param( + "axes3d.trackballsize", + default=0.667, + validator=validate_float, + description="trackball diameter, in units of the Axes bbox" + ), + _Param( + "axes3d.trackballborder", + default=0.2, + validator=validate_float, + description="trackball border width, in units of the Axes bbox (only for " + "'sphere' and 'arcball' style)" + ), + _Param( + "xaxis.labellocation", + default="center", + validator=["left", "center", "right"], + description="alignment of the xaxis label: {left, right, center}" + ), + _Param( + "yaxis.labellocation", + default="center", + validator=["bottom", "center", "top"], + description="alignment of the yaxis label: {bottom, top, center}" + ), + _Param("date.autoformatter.year", "%Y", validate_string), + _Param("date.autoformatter.month", "%Y-%m", validate_string), + _Param("date.autoformatter.day", "%Y-%m-%d", validate_string), + _Param("date.autoformatter.hour", "%m-%d %H", validate_string), + _Param("date.autoformatter.minute", "%d %H:%M", validate_string), + _Param("date.autoformatter.second", "%H:%M:%S", validate_string), + _Param("date.autoformatter.microsecond", "%M:%S.%f", validate_string), + _Param( + "date.epoch", + default="1970-01-01T00:00:00", + validator=_validate_date, + description="The reference date for Matplotlib's internal date representation. " + "See https://matplotlib.org/stable/gallery/ticks/date_precision_and_epochs.html"), #noqa + _Param( + "date.converter", + default="auto", + validator=["auto", "concise"], + description="'auto', 'concise'" + ), + _Param( + "date.interval_multiples", + default=True, + validator=validate_bool, + description="For auto converter whether to use interval_multiples" + ), + _Param( + "xtick.top", + default=False, + validator=validate_bool, + description="draw ticks on the top side" + ), + _Param( + "xtick.bottom", + default=True, + validator=validate_bool, + description="draw ticks on the bottom side" + ), + _Param( + "xtick.labeltop", + default=False, + validator=validate_bool, + description="draw label on the top" + ), + _Param( + "xtick.labelbottom", + default=True, + validator=validate_bool, + description="draw label on the bottom" + ), + _Param( + "xtick.major.size", + default=3.5, + validator=validate_float, + description="major tick size in points" + ), + _Param( + "xtick.minor.size", + default=2.0, + validator=validate_float, + description="minor tick size in points" + ), + _Param( + "xtick.major.width", + default=0.8, + validator=validate_float, + description="major tick width in points" + ), + _Param( + "xtick.minor.width", + default=0.6, + validator=validate_float, + description="minor tick width in points" + ), + _Param( + "xtick.major.pad", + default=3.5, + validator=validate_float, + description="distance to major tick label in points" + ), + _Param( + "xtick.minor.pad", + default=3.4, + validator=validate_float, + description="distance to the minor tick label in points" + ), + _Param( + "xtick.color", + default="black", + validator=validate_color, + description="color of the ticks" + ), + _Param( + "xtick.labelcolor", + default="inherit", + validator=validate_color_or_inherit, + description="color of the tick labels or inherit from xtick.color" + ), + _Param( + "xtick.labelsize", + default="medium", + validator=validate_fontsize, + description="font size of the tick labels" + ), + _Param( + "xtick.direction", + default="out", + validator=["out", "in", "inout"], + description="direction: {in, out, inout}" + ), + _Param( + "xtick.minor.visible", + default=False, + validator=validate_bool, + description="visibility of minor ticks on x-axis" + ), + _Param( + "xtick.major.top", + default=True, + validator=validate_bool, + description="draw x axis top major ticks" + ), + _Param( + "xtick.major.bottom", + default=True, + validator=validate_bool, + description="draw x axis bottom major ticks" + ), + _Param( + "xtick.minor.top", + default=True, + validator=validate_bool, + description="draw x axis top minor ticks" + ), + _Param( + "xtick.minor.bottom", + default=True, + validator=validate_bool, + description="draw x axis bottom minor ticks" + ), + _Param( + "xtick.minor.ndivs", + default="auto", + validator=_validate_minor_tick_ndivs, + description="number of minor ticks between the major ticks on x-axis" + ), + _Param( + "xtick.alignment", + default="center", + validator=["center", "right", "left"], + description="alignment of xticks" + ), + _Param( + "ytick.left", + default=True, + validator=validate_bool, + description="draw ticks on the left side" + ), + _Param( + "ytick.right", + default=False, + validator=validate_bool, + description="draw ticks on the right side" + ), + _Param( + "ytick.labelleft", + default=True, + validator=validate_bool, + description="draw tick labels on the left side" + ), + _Param( + "ytick.labelright", + default=False, + validator=validate_bool, + description="draw tick labels on the right side" + ), + _Param( + "ytick.major.size", + default=3.5, + validator=validate_float, + description="major tick size in points" + ), + _Param( + "ytick.minor.size", + default=2.0, + validator=validate_float, + description="minor tick size in points" + ), + _Param( + "ytick.major.width", + default=0.8, + validator=validate_float, + description="major tick width in points" + ), + _Param( + "ytick.minor.width", + default=0.6, + validator=validate_float, + description="minor tick width in points" + ), + _Param( + "ytick.major.pad", + default=3.5, + validator=validate_float, + description="distance to major tick label in points" + ), + _Param( + "ytick.minor.pad", + default=3.4, + validator=validate_float, + description="distance to the minor tick label in points" + ), + _Param( + "ytick.color", + default="black", + validator=validate_color, + description="color of the ticks" + ), + _Param( + "ytick.labelcolor", + default="inherit", + validator=validate_color_or_inherit, + description="color of the tick labels or inherit from ytick.color" + ), + _Param( + "ytick.labelsize", + default="medium", + validator=validate_fontsize, + description="font size of the tick labels" + ), + _Param( + "ytick.direction", + default="out", + validator=["out", "in", "inout"], + description="direction: {in, out, inout}" + ), + _Param( + "ytick.minor.visible", + default=False, + validator=validate_bool, + description="visibility of minor ticks on y-axis" + ), + _Param( + "ytick.major.left", + default=True, + validator=validate_bool, + description="draw y axis left major ticks" + ), + _Param( + "ytick.major.right", + default=True, + validator=validate_bool, + description="draw y axis right major ticks" + ), + _Param( + "ytick.minor.left", + default=True, + validator=validate_bool, + description="draw y axis left minor ticks" + ), + _Param( + "ytick.minor.right", + default=True, + validator=validate_bool, + description="draw y axis right minor ticks" + ), + _Param( + "ytick.minor.ndivs", + default="auto", + validator=_validate_minor_tick_ndivs, + description="number of minor ticks between the major ticks on y-axis" + ), + _Param("ytick.alignment", "center_baseline", + ["center", "top", "bottom", "baseline", "center_baseline"], + description="alignment of yticks" + ), + _Param( + "grid.color", + default="#b0b0b0", + validator=validate_color, + description='b0b0b0" # grid color' + ), + _Param( + "grid.linestyle", + default="-", + validator=_validate_linestyle, + description="solid" + ), + _Param( + "grid.linewidth", + default=0.8, + validator=validate_float, + description="in points" + ), + _Param( + "grid.alpha", + default=1.0, + validator=validate_float, + description="transparency, between 0.0 and 1.0" + ), + _Param( + "grid.major.color", + default=None, + validator=_validate_color_or_None, + description="If None defaults to grid.color" + ), + _Param( + "grid.major.linestyle", + default=None, + validator=_validate_linestyle_or_None, + description="If None defaults to grid.linestyle" + ), + _Param( + "grid.major.linewidth", + default=None, + validator=validate_float_or_None, + description="If None defaults to grid.linewidth" + ), + _Param( + "grid.major.alpha", + default=None, + validator=validate_float_or_None, + description="If None defaults to grid.alpha" + ), + _Param( + "grid.minor.color", + default=None, + validator=_validate_color_or_None, + description="If None defaults to grid.color" + ), + _Param( + "grid.minor.linestyle", + default=None, + validator=_validate_linestyle_or_None, + description="If None defaults to grid.linestyle" + ), + _Param( + "grid.minor.linewidth", + default=None, + validator=validate_float_or_None, + description="If None defaults to grid.linewidth" + ), + _Param( + "grid.minor.alpha", + default=None, + validator=validate_float_or_None, + description="If None defaults to grid.alpha" + ), + _Param( + "legend.loc", + default="best", + validator=_validate_legend_loc + ), + _Param( + "legend.frameon", + default=True, + validator=validate_bool, + description="if True, draw the legend on a background patch" + ), + _Param( + "legend.framealpha", + default=0.8, + validator=validate_float_or_None, + description="legend patch transparency" + ), + _Param( + "legend.facecolor", + default="inherit", + validator=validate_color_or_inherit, + description="inherit from axes.facecolor; or color spec" + ), + _Param( + "legend.edgecolor", + default="0.8", + validator=validate_color_or_inherit, + description="background patch boundary color" + ), + _Param( + "legend.linewidth", + default=None, + validator=validate_float_or_None, + description="line width of the legend frame, None means inherit from " + "patch.linewidth" + ), + _Param( + "legend.fancybox", + default=True, + validator=validate_bool, + description="if True, use a rounded box for the legend background, else a " + "rectangle" + ), + _Param( + "legend.shadow", + default=False, + validator=validate_bool, + description="if True, give background a shadow effect" + ), + _Param( + "legend.numpoints", + default=1, + validator=validate_int, + description="the number of marker points in the legend line" + ), + _Param( + "legend.scatterpoints", + default=1, + validator=validate_int, + description="number of scatter points" + ), + _Param( + "legend.markerscale", + default=1.0, + validator=validate_float, + description="the relative size of legend markers vs. original" + ), + _Param( + "legend.fontsize", + default="medium", + validator=validate_fontsize + ), + _Param( + "legend.labelcolor", + default="None", + validator=_validate_color_or_linecolor + ), + _Param( + "legend.title_fontsize", + default=None, + validator=validate_fontsize_None, + description="None sets to the same as the default axes." + ), + _Param( + "legend.borderpad", + default=0.4, + validator=validate_float, + description="border whitespace" + ), + _Param( + "legend.labelspacing", + default=0.5, + validator=validate_float, + description="the vertical space between the legend entries" + ), + _Param( + "legend.handlelength", + default=2.0, + validator=validate_float, + description="the length of the legend lines" + ), + _Param( + "legend.handleheight", + default=0.7, + validator=validate_float, + description="the height of the legend handle" + ), + _Param( + "legend.handletextpad", + default=0.8, + validator=validate_float, + description="the space between the legend line and legend text" + ), + _Param( + "legend.borderaxespad", + default=0.5, + validator=validate_float, + description="the border between the axes and legend edge" + ), + _Param( + "legend.columnspacing", + default=2.0, + validator=validate_float, description="column separation" + ), + _Param( + "figure.titlesize", + default="large", + validator=validate_fontsize, + description="size of the figure title (``Figure.suptitle()``)" + ), + _Param( + "figure.titleweight", + default="normal", + validator=validate_fontweight, + description="weight of the figure title" + ), + _Param( + "figure.labelsize", + default="large", + validator=validate_fontsize, + description="size of the figure label (``Figure.sup[x|y]label()``)" + ), + _Param( + "figure.labelweight", + default="normal", + validator=validate_fontweight, + description="weight of the figure label" + ), + _Param( + "figure.figsize", + default=[6.4, 4.8], + validator=_listify_validator(validate_float, n=2), + description="figure size in inches" + ), + _Param( + "figure.dpi", + default=100.0, + validator=validate_float, description="figure dots per inch" + ), + _Param( + "figure.facecolor", + default="white", + validator=validate_color, description="figure face color" + ), + _Param( + "figure.edgecolor", + default="white", + validator=validate_color, description="figure edge color" + ), + _Param( + "figure.frameon", + default=True, + validator=validate_bool, description="enable figure frame" + ), + _Param( + "figure.max_open_warning", + default=20, + validator=validate_int, + description="The maximum number of figures to open through the pyplot " + "interface before emitting a warning. If less than one this " + "feature is disabled." + ), + _Param( + "figure.raise_window", + default=True, + validator=validate_bool, + description="Raise the GUI window to front when show() is called. If set to " + "False, we currently do not take any further actions and whether " + "the window appears on the front may depend on the GUI framework " + "and window manager." + ), + _Param( + "figure.subplot.left", + default=0.125, + validator=validate_float, + description="the left side of the subplots of the figure" + ), + _Param( + "figure.subplot.right", + default=0.9, + validator=validate_float, + description="the right side of the subplots of the figure" + ), + _Param( + "figure.subplot.bottom", + default=0.11, + validator=validate_float, + description="the bottom of the subplots of the figure" + ), + _Param( + "figure.subplot.top", + default=0.88, + validator=validate_float, + description="the top of the subplots of the figure" + ), + _Param( + "figure.subplot.wspace", + default=0.2, + validator=validate_float, + description="the amount of width reserved for space between subplots, " + "expressed as a fraction of the average axis width" + ), + _Param( + "figure.subplot.hspace", + default=0.2, + validator=validate_float, + description="the amount of height reserved for space between subplots, " + "expressed as a fraction of the average axis height" + ), + _Param( + "figure.autolayout", + default=False, + validator=validate_bool, + description="When True, automatically adjust subplot parameters to make the " + "plot fit the figure using `~.Figure.tight_layout`" + ), + _Param( + "figure.constrained_layout.use", + default=False, + validator=validate_bool, + description="When True, automatically make plot elements fit on the figure. " + '(Not compatible with "figure.autolayout", above).' + ), + _Param( + "figure.constrained_layout.h_pad", + default=0.04167, + validator=validate_float, + description="Padding (in inches) around axes; defaults to 3/72 inches, " + "i.e. 3 points" + ), + _Param( + "figure.constrained_layout.w_pad", + default=0.04167, + validator=validate_float, + description="Padding (in inches) around axes; defaults to 3/72 inches, " + "i.e. 3 points" + ), + _Param( + "figure.constrained_layout.hspace", + default=0.02, + validator=validate_float, + description="Spacing between subplots, relative to the subplot sizes. Much " + "smaller than for tight_layout (figure.subplot.hspace, " + "figure.subplot.wspace) as constrained_layout already takes " + "surrounding texts (titles, labels, # ticklabels) into account." + ), + _Param( + "figure.constrained_layout.wspace", + default=0.02, + validator=validate_float, + description="Spacing between subplots, relative to the subplot sizes. Much " + "smaller than for tight_layout (figure.subplot.hspace, " + "figure.subplot.wspace) as constrained_layout already takes " + "surrounding texts (titles, labels, # ticklabels) into account." + ), + _Param( + "image.aspect", + default="equal", + validator=validate_aspect, + description="{equal, auto} or a number" + ), + _Param( + "image.interpolation", + default="auto", + validator=validate_string, + description="see help(imshow) for options" + ), + _Param( + "image.interpolation_stage", + default="auto", + validator=["auto", "data", "rgba"], + description="see help(imshow) for options" + ), + _Param( + "image.cmap", + default="viridis", + validator=_validate_cmap, + description="A colormap name (plasma, magma, etc.)" + ), + _Param( + "image.lut", + default=256, + validator=validate_int, + description="the size of the colormap lookup table" + ), + _Param( + "image.origin", + default="upper", + validator=["upper", "lower"], description="{lower, upper}" + ), + _Param( + "image.resample", + default=True, + validator=validate_bool + ), + _Param( + "image.composite_image", + default=True, + validator=validate_bool, + description="When True, all the images on a set of axes are combined into a " + "single composite image before saving a figure as a vector " + "graphics file, such as a PDF." + ), + _Param( + "contour.negative_linestyle", + default="dashed", + validator=_validate_linestyle, + description="string or on-off ink sequence" + ), + _Param( + "contour.corner_mask", + default=True, + validator=validate_bool, description="{True, False}" + ), + _Param( + "contour.linewidth", + default=None, + validator=validate_float_or_None, + description="{float, None} Size of the contour line widths. If set to None, it " + 'falls back to "line.linewidth".' + ), + _Param( + "contour.algorithm", + default="mpl2014", + validator=["mpl2005", "mpl2014", "serial", "threaded"], + description="{mpl2005, mpl2014, serial, threaded}" + ), + _Param( + "errorbar.capsize", + default=0.0, + validator=validate_float, + description="length of end cap on error bars in pixels" + ), + _Param( + "hist.bins", + default=10, + validator=validate_hist_bins, + description="The default number of histogram bins or 'auto'." + ), + _Param( + "scatter.marker", + default="o", + validator=_validate_marker, + description="The default marker type for scatter plots." + ), + _Param( + "scatter.edgecolors", + default="face", + validator=validate_string, + description="The default edge colors for scatter plots." + ), + _Param( + "agg.path.chunksize", + default=0, + validator=validate_int, + description="0 to disable; values in the range 10000 to 100000 can improve " + "speed slightly and prevent an Agg rendering failure when plotting " + "very large data sets, especially if they are very gappy. It may " + "cause minor artifacts, though. A value of 20000 is probably a " + "good starting point." + ), + _Param( + "path.simplify", + default=True, + validator=validate_bool, + description='When True, simplify paths by removing "invisible" points to ' + 'reduce file size and increase rendering speed', + ), + _Param( + "path.simplify_threshold", + default=0.111111111111, + validator=_validate_greaterequal0_lessequal1, + description="The threshold of similarity below which vertices will be removed " + "in the simplification process." + ), + _Param( + "path.snap", + default=True, + validator=validate_bool, + description="When True, rectilinear axis-aligned paths will be snapped to the " + "nearest pixel when certain criteria are met. When False, paths " + "will never be snapped." + ), + _Param( + "path.sketch", + default=None, + validator=validate_sketch, + description="May be None, or a tuple of the form:" + "path.sketch: (scale, length, randomness)" + "- *scale* is the amplitude of the wiggle perpendicular to the line" + " (in pixels)." + "- *length* is the length of the wiggle along the line (in pixels)." + "- *randomness* is the factor by which the length is randomly " + " scaled." + ), + _Param( + "path.effects", + default=[], + validator=validate_anylist + ), + _Param( + "savefig.dpi", + default="figure", + validator=validate_dpi, + description="figure dots per inch or 'figure'" + ), + _Param( + "savefig.facecolor", + default="auto", + validator=validate_color_or_auto, + description="figure face color when saving" + ), + _Param( + "savefig.edgecolor", + default="auto", + validator=validate_color_or_auto, + description="figure edge color when saving" + ), + _Param( + "savefig.format", + default="png", + validator=validate_string, description="{png, ps, pdf, svg}" + ), + _Param( + "savefig.bbox", + default=None, + validator=validate_bbox, + description="{tight, standard} 'tight' is incompatible with generating frames " + "for animation" + ), + _Param( + "savefig.pad_inches", + default=0.1, + validator=validate_float, + description="padding to be used, when bbox is set to 'tight'" + ), + _Param( + "savefig.directory", + default="~", + validator=_validate_pathlike, + description="default directory in savefig dialog, gets updated after " + "interactive saves, unless set to the empty string (i.e. the " + "current directory); use '.' to start at the current directory but " + "update after interactive saves" + ), + _Param( + "savefig.transparent", + default=False, + validator=validate_bool, + description="whether figures are saved with a transparent background by default" + + ), + _Param( + "savefig.orientation", + default="portrait", + validator=["landscape", "portrait"], + description="orientation of saved figure, for PostScript output only" + ), + _Param( + "macosx.window_mode", + default="system", + validator=["system", "tab", "window"], + description="How to open new figures (system, tab, window) system uses " + "the MacOS system preferences" + ), + _Param( + "tk.window_focus", + default=False, + validator=validate_bool, + description="Maintain shell focus for TkAgg" + ), + _Param( + "ps.papersize", + default="letter", + validator=_ignorecase( + ["figure", "letter", "legal", "ledger", + *[f"{ab}{i}" for ab in "ab" for i in range(11)], + ], + ), + description="{figure, letter, legal, ledger, A0-A10, B0-B10}" + ), + _Param( + "ps.useafm", + default=False, + validator=validate_bool, + description="use AFM fonts, results in small files" + ), + _Param( + "ps.usedistiller", + default=None, + validator=validate_ps_distiller, + description="{ghostscript, xpdf, None} Experimental: may produce smaller " + "files. xpdf intended for production of publication quality files, " + "but requires ghostscript, xpdf and ps2eps" + ), + _Param( + "ps.distiller.res", + default=6000, + validator=validate_int, description="dpi" + ), + _Param( + "ps.fonttype", + default=3, + validator=validate_fonttype, + description="Output Type 3 (Type3) or Type 42 (TrueType)" + ), + _Param( + "pdf.compression", + default=6, + validator=validate_int, + description="integer from 0 to 9 0 disables compression (good for debugging)" + ), + _Param( + "pdf.fonttype", + default=3, + validator=validate_fonttype, + description="Output Type 3 (Type3) or Type 42 (TrueType)" + ), + _Param( + "pdf.use14corefonts", + default=False, + validator=validate_bool + ), + _Param( + "pdf.inheritcolor", + default=False, + validator=validate_bool + ), + _Param( + "svg.image_inline", + default=True, + validator=validate_bool, + description="Write raster image data directly into the SVG file" + ), + _Param( + "svg.fonttype", + default="path", + validator=["none", "path"], + description="How to handle SVG fonts: " + "path: Embed characters as paths -- supported by most SVG " + " renderers" + "none: Assume fonts are installed on the machine where the SVG " + "will be viewed." + ), + _Param( + "svg.hashsalt", + default=None, + validator=validate_string_or_None, + description="If not None, use this string as hash salt instead of uuid4" + ), + _Param( + "svg.id", + default=None, + validator=validate_string_or_None, + description="If not None, use this string as the value for the `id` attribute " + "in the top tag" + ), + _Param( + "pgf.rcfonts", + default=True, + validator=validate_bool + ), + _Param( + "pgf.preamble", + default="", + validator=validate_string, + description="See text.latex.preamble for documentation" + ), + _Param( + "pgf.texsystem", + default="xelatex", + validator=["xelatex", "lualatex", "pdflatex"] + ), + _Param( + "docstring.hardcopy", + default=False, + validator=validate_bool, + description="set this when you want to generate hardcopy docstring" + ), + _Param( + "keymap.fullscreen", + default=["f", "ctrl+f"], + validator=validate_stringlist, + description="toggling" + ), + _Param( + "keymap.home", + default=["h", "r", "home"], + validator=validate_stringlist, + description="home or reset mnemonic" + ), + _Param( + "keymap.back", + default=["left", "c", "backspace", "MouseButton.BACK"], + validator=validate_stringlist, description="forward / backward keys" + ), + _Param( + "keymap.forward", + default=["right", "v", "MouseButton.FORWARD"], + validator=validate_stringlist, + description="for quick navigation" + ), + _Param( + "keymap.pan", + default=["p"], + validator=validate_stringlist, description="pan mnemonic" + ), + _Param( + "keymap.zoom", + default=["o"], + validator=validate_stringlist, description="zoom mnemonic" + ), + _Param( + "keymap.save", + default=["s", "ctrl+s"], + validator=validate_stringlist, + description="saving current figure" + ), + _Param( + "keymap.help", + default=["f1"], + validator=validate_stringlist, + description="display help about active tools" + ), + _Param( + "keymap.quit", + default=["ctrl+w", "cmd+w", "q"], + validator=validate_stringlist, + description="close the current figure" + ), + _Param( + "keymap.quit_all", + default=[], + validator=validate_stringlist, description="close all figures" + ), + _Param( + "keymap.grid", + default=["g"], + validator=validate_stringlist, + description="switching on/off major grids in current axes" + ), + _Param( + "keymap.grid_minor", + default=["G"], + validator=validate_stringlist, + description="switching on/off minor grids in current axes" + ), + _Param( + "keymap.yscale", + default=["l"], + validator=validate_stringlist, + description="toggle scaling of y-axes ('log'/'linear')" + ), + _Param( + "keymap.xscale", + default=["k", "L"], + validator=validate_stringlist, + description="toggle scaling of x-axes ('log'/'linear')" + ), + _Param( + "keymap.copy", + default=["ctrl+c", "cmd+c"], + validator=validate_stringlist, + description="copy figure to clipboard" + ), + _Param( + "animation.html", + default="none", + validator=["html5", "jshtml", "none"], + description="How to display the animation as HTML in the IPython notebook: " + "- 'html5' uses HTML5 video tag " + "- 'jshtml' creates a JavaScript animation" + ), + _Param( + "animation.writer", + default="ffmpeg", + validator=validate_string, + description="MovieWriter 'backend' to use" + ), + _Param( + "animation.codec", + default="h264", + validator=validate_string, + description="Codec to use for writing movie" + ), + _Param( + "animation.bitrate", + default=-1, + validator=validate_int, + description="Controls size/quality trade-off for movie. -1 implies let " + "utility auto-determine" + ), + _Param("animation.frame_format", "png", + ["png", "jpeg", "tiff", "raw", "rgba", "ppm", "sgi", "bmp", "pbm", "svg"], + description="Controls frame format used by temp files" + ), + _Param( + "animation.ffmpeg_path", + default="ffmpeg", + validator=_validate_pathlike, + description="Path to ffmpeg binary. Unqualified paths are resolved by " + "subprocess.Popen." + ), + _Param( + "animation.ffmpeg_args", + default=[], + validator=validate_stringlist, + description="Additional arguments to pass to ffmpeg" + ), + _Param( + "animation.convert_path", + default="convert", + validator=_validate_pathlike, + description="Path to ImageMagick's convert binary. Unqualified paths are " + "resolved by subprocess.Popen, except that on Windows, we look up " + "an install of ImageMagick in the registry (as convert is also the " + "name of a system tool)." + ), + _Param( + "animation.convert_args", + default=["-layers", "OptimizePlus"], + validator=validate_stringlist, + description="Additional arguments to pass to convert" + ), + _Param( + "animation.embed_limit", + default=20.0, + validator=validate_float, + description="Limit, in MB, of size of base64 encoded animation in HTML (i.e. " + "IPython notebook)" + ), + _Param( + "_internal.classic_mode", + default=False, + validator=validate_bool + ), + _Param("backend", None, validate_backend), +] + + +def _generate_rst(): # pragma: no cover + """ + Generate rst documentation for rcParams. + + Note: The style is very simple, but will be refined later. + """ + + text = """\ +.. + autogenerated rcParams documentation. Update via + > python -c "from matplotlib import rcsetup; rcsetup._write_rcParam_rst()" + +""" + for param in _params: + text += f""" +.. _rcparam_{param.name.replace('.', '_')}: + +{param.name}: ``{param.default!r}`` + {param.description if param.description else "*no description*"} +""" + return text + + +def _write_rcParam_rst(): # pragma: no cover + """Write the generated rst documentation to /users/_rcparams_generated.rst.""" + docpath = Path(os.path.dirname(__file__)) / "../../doc" + if not docpath.exists(): + raise RuntimeError("Cannot find the doc/api/ directory") + + (docpath / "users/_rcparams_generated.rst").write_text(_generate_rst()) diff --git a/lib/matplotlib/rcsetup.pyi b/lib/matplotlib/rcsetup.pyi index c6611845723d..120c0c93bec9 100644 --- a/lib/matplotlib/rcsetup.pyi +++ b/lib/matplotlib/rcsetup.pyi @@ -33,6 +33,7 @@ def validate_string_or_None(s: Any) -> str | None: ... def validate_stringlist(s: Any) -> list[str]: ... def validate_int(s: Any) -> int: ... def validate_int_or_None(s: Any) -> int | None: ... +def validate_intlist(s: Any) -> list[int]: ... def validate_float(s: Any) -> float: ... def validate_float_or_None(s: Any) -> float | None: ... def validate_floatlist(s: Any) -> list[float]: ... diff --git a/lib/matplotlib/sphinxext/roles.py b/lib/matplotlib/sphinxext/roles.py index c3e57ebc3aec..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) diff --git a/lib/matplotlib/testing/__init__.py b/lib/matplotlib/testing/__init__.py index eff079efe887..a838a6b09b32 100644 --- a/lib/matplotlib/testing/__init__.py +++ b/lib/matplotlib/testing/__init__.py @@ -92,6 +92,13 @@ def subprocess_run_for_testing(command, env=None, timeout=60, stdout=None, """ 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, @@ -144,13 +151,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 diff --git a/lib/matplotlib/tests/baseline_images/test_axes/imshow.pdf b/lib/matplotlib/tests/baseline_images/test_axes/imshow.pdf index 183b072fc312..64f4e3519717 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..d709d9f03f47 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..b0bcc2358e3a 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 @@ - - + + + + + + 2025-09-29T14:55:05.029228 + image/svg+xml + + + Matplotlib v3.11.0.dev1393+gfd8d60293, 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.png b/lib/matplotlib/tests/baseline_images/test_axes/imshow_clip.png index cde64b03c7f6..d6cdbd5c07f1 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..d92dfd8613de 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 + 2025-09-29T14:55:05.618523 image/svg+xml - Matplotlib v3.3.4.post2495+g8432e3164, https://matplotlib.org/ + Matplotlib v3.11.0.dev1393+gfd8d60293, 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(#p94ac23f9ea)" 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/use_colorizer_keyword.png b/lib/matplotlib/tests/baseline_images/test_axes/use_colorizer_keyword.png index c1c8074ed80c..9d6f8b9c6f63 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_colors/light_source_shading_topo.png b/lib/matplotlib/tests/baseline_images/test_colors/light_source_shading_topo.png index 222ebca02d82..dbfb1d425665 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 index 077365674ac2..7f621591e000 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_colors/test_norm_abc.png and b/lib/matplotlib/tests/baseline_images/test_colors/test_norm_abc.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..ad7f762c7a0b 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..eef9639a3324 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..1e93d3be191b 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 @@ - - + + + + + + 2025-09-29T15:01:35.415119 + image/svg+xml + + + Matplotlib v3.11.0.dev1393+gfd8d60293, 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..358e5ef2d88f 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/image_cliprect.png b/lib/matplotlib/tests/baseline_images/test_image/image_cliprect.png index 4552df3515cd..be33cdd70a99 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_image/image_cliprect.png and b/lib/matplotlib/tests/baseline_images/test_image/image_cliprect.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_image/image_cliprect.svg b/lib/matplotlib/tests/baseline_images/test_image/image_cliprect.svg index f8d96c2e312b..50fe1d126a74 100644 --- a/lib/matplotlib/tests/baseline_images/test_image/image_cliprect.svg +++ b/lib/matplotlib/tests/baseline_images/test_image/image_cliprect.svg @@ -1,12 +1,23 @@ - - + + + + + + 2025-10-08T04:51:13.682947 + image/svg+xml + + + Matplotlib v3.11.0.dev1425+gb39ccbe8f, 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..8a09bd6a5a56 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..3ba3c6e53116 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..d977b05b0253 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 @@ - - + + + + + + 2025-07-10T19:29:50.453941 + 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,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/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_masked_interpolation.pdf b/lib/matplotlib/tests/baseline_images/test_image/imshow_masked_interpolation.pdf index 0342a2baa4b2..d93b352994ab 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..0fadbb4b1cc4 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..e6638ad21189 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 + 2025-09-29T15:01:41.159575 image/svg+xml - Matplotlib v3.9.0.dev1543+gdd88cca65b.d20240423, https://matplotlib.org/ + Matplotlib v3.11.0.dev1393+gfd8d60293, https://matplotlib.org/ @@ -29,167 +29,167 @@ z " style="fill: #ffffff"/> - + +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" id="imageaf5fd25eb2" transform="scale(1 -1) translate(0 -51.12)" x="57.6" y="-54.994689" width="51.12" height="51.12"/> - + +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" id="image4cd56d3e07" transform="scale(1 -1) translate(0 -51.12)" x="118.820571" y="-54.994689" width="51.12" height="51.12"/> - + +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" id="image4a7ce4b4ad" transform="scale(1 -1) translate(0 -51.12)" x="180.041143" y="-54.994689" width="51.12" height="51.12"/> - + +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" id="image91d0d5f5b0" transform="scale(1 -1) translate(0 -51.12)" x="241.261714" y="-54.994689" width="51.12" height="51.12"/> - + +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" id="imagea49606cabe" transform="scale(1 -1) translate(0 -51.12)" x="302.482286" y="-54.994689" width="51.12" height="51.12"/> - + +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" id="imagecc5128dabe" transform="scale(1 -1) translate(0 -51.12)" x="363.702857" y="-54.994689" width="51.12" height="51.12"/> - + +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" id="image30ab40c936" transform="scale(1 -1) translate(0 -51.12)" x="57.6" y="-148.916571" width="51.12" height="51.12"/> - + +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" id="image3f235855ab" transform="scale(1 -1) translate(0 -51.12)" x="118.820571" y="-148.916571" width="51.12" height="51.12"/> - + +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" id="imagea8929fed79" transform="scale(1 -1) translate(0 -51.12)" x="180.041143" y="-148.916571" width="51.12" height="51.12"/> - + +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" id="image7599b6a708" transform="scale(1 -1) translate(0 -51.12)" x="241.261714" y="-148.916571" width="51.12" height="51.12"/> - + +iVBORw0KGgoAAAANSUhEUgAAAEcAAABHCAYAAABVsFofAAANAUlEQVR4nM2be4xcVR3HP+fcOzM7M+0ultKW2u7yaC1UpAWK+ERii0U0MSigRE0kJgaNiIIoiv4hGgIJovFBjBqRGA2+8A9CDD5SFKKivDS6IKylpbTbKuxuS+dx595zjn/cx9yZe+7sTNvd4ZfcdPac3/md3/me7+/3O/fMVDy/Z5UhEkcIukVmWiJdLLqW8bm6OZZtPuTpS4vd0IZF98Qpq24vEbtT4CTGrRPmOW2XxQQvT9cGng04sIPneilo4mF+9G9VCmra0DSSunEBOMFpcdLa/cmY7z31Jm59Yjvlv1apHNDUV0pqWxp86qzfcfXpOxK9Q3vXsDsw1IzLCdKjKgVLRIG68ZP5IA2UTtqWr9lnXdBCSXPfyUgEbtPYeAJLpaIiHKRU/L25gt/MnYHEcMPK33fo3XLvJey8/trM+Ds+X+ZqdnS0fe/FN+OgecPSKd5eeYGKLFJXPnVjUnvf3i27Zwsvngm3y61FjOiWilEA1I3hv8Eouw4vCztWdulN20Nl6XM607a/OUozKLC+fIC6maZCEYCmyQu3TMQvitSjtYvf73yV1YOmKSSfJ9xZJtwQxIeaVX76wnn8a2YVW07Ywx1bfpw7yfVPXMof95/Kmcfv44rjH+bNIwEAe1WdXcGSRK8qWlYgpAjbzpp4ftD1HZVMPbcKALdmiplOB8Mj9ZN5ZHaCM8b2cfGmXyV9k5Pb+OF5dwLwN+AO8sG5bfPPAfgrUH7sCt569t0AnATcM7mNv8ydwuuO28m55Z1WH4ZEHOpRqnFrutR2JiX7vOOYenE5I67f0T5V74qrPuWpg53jnq6v4t8vrGB5scbG0t7QB5ENxWFInIdlUxdo6gI1XUyepimwpjTLeat3s2HJgY6Ba0dm2D72YQC2b/x8z0ku2HoLANvk5bx6bLqjb2LkRc5e+TxrR2ZomgJNU6CmS8kTt6XDe7GkZorUTBHx7SffklAmjnGAi6tPM752P419E5z+y4+z++pPA/D8nhN5ZaqUix7cN13njFj3pK99lcn3fpPy6t009k3wq8MrEh1pYc8V6x8ddH1HJb/+z+kAiK9ObrMcAg3XbGyX7DQAeQu2SS/ddN/dz5xDPQrvWGTqnHPlhr/kzrEQ8supzQC4dRXlnCHGez0KJQCH4eedpg5D2a3rsFp1g/PxR69gplWl7Pj8ZvlpXHjKvwH4xGPv40/fOJfjf/EP9n9oE+Zb9jMKwMbP3c7arz/G3CWbOefaxzFbQt0ndq/h+tlL2ds4jleW51gevBT6YC3niw9WvFHi2scvy3jkaZd/3nAmf/jtDUBnCJz/jlt58L7PJH93h05aukMurXvRxs9x/+TNbHv9lzn925MsLxzOjJfR+C++5r6+FnWs5OuTWwFwG6p9zol3qaGKCTDdkgbmaOT+yZsB+N2fv8hY4yoqshX5MKTDTUqa0VuD6+ns68NBf2RRnTnUGsErZ/0YFlBxHnYbKnuOaFraFlI85dLQnSd1OazjMRDnYbepwh0bJp2lMMSbNExQYomjSba0S0u7NFUheVrKYesbvmId+NYLbj4mDlx0Snio3H7GFyg5AZ5yQwapAg1VwNNu8iy2xD64XtCeXKTY4980x+vVZym5AZeXP8KZS8P3H/3fF5l4z21U9jnUxgMa75qgvHq3dZLxO2+l8kyR5krN+jP3cNPyd1KRLaYaKzCHfS5Q14ETMOL4vBwYHEtDFXCEwfUip0SXU3dt+HFy4ycw/Dxq373nRMa3pl4fPtrjhHzl/CdkPb2Od09dSFMVXhbAALQitrq+7rxTjZdT0/a71hntMn4MHNDT65AnTjGr6rS0i6/b937DzjvNIMp/rcDFV07ytKLntv1v456pzVz3+GW85hNfSwZ+88DWI5rwO0+dn3ze9LHbuX7/WTz47Cnc/L83csgbwUv5EecfT7m09OJflra0Q0s7uIGyM+SBqfXsePTVUA341FX3cc03wnvidc/e1PNFNC1pvYmHb0l0f/TJ8/jSY+/kF4e2UBjzWDZaz4R1LMMItTjVyDRrfOUQaEmgJfpQgeouBzFT4AOjTyYD/dkjOyC60+1zzAdf9TD+bInKrgL+zAi+krQCJ3kC1X5aweJXKy9w8QIXqZQk/QSBQxA4UNJ4xxt0WfOTlza0R5aO7EUwqLbH/eTpc0P7ywyM6HBTOgCJQZLkMXshJSaIOPXuL2d5G1FZqzDeiyWfcil89zlcHyGYruDWBP6Y5i1bJrkrulNOy8ceeT/3PbqJwqxDUNW4KxpJ+NS8YrQJkkJBWR1Mf3f35CVfOtr1DiSb7r0RAFd17UzsVLHkU642aXhFmtNV3GdHwwFvPMgz14QHuEN713DZhq32C68t8ORzqzltPLwe3XzvjdR2hDd+tXHFkrWHGKs2qHlFPK/zdSUv/yyW+BEppFGC9KOD8Gm1XJqtAkpJEKBKoEag5RV44Nl1ADzUHIP1E9YJLlp9NQ/WTwXgmedWhbYiGwCeV6AeAaOURKceFTiowEn+XmyJU4wY/8Et7W3qKjzC1RglIJCgos6SojrW5LhKg7l6GW9qlBV/04ztmOL+/32H7SdcxUvnr+PAayUjp82xrNLgJa/I7OwSTDMqy65BuBrH1WjVPWnW2V3vv/EYLn1+Wfezm0JXxr97ay44+W05tM+r6gPYsP7WYID5ckPSqmtXjedz0Tka+ceXbOcgYImczsiG6UvXZjce3/XK0lO3t2+uCISls7cjJgOGyB/fq91qwwJ0X3bt401P3Ty7EXNEd8zHRnsMFP3sZF57jm4b8Hk2q1e7rfOIwE6YY1eyjQ8BswXv/D522ujWtQA+oN18hh+53XZY9TFp/LEXq+ab1G5j8NDO9UOk68uR2Y3bXevXQvMYSncbm9LLAegBQ9LmhyuD+ZVyjVrCb6Dx2KuJ6Rqw87PX5RhdGDn59tsAS1hlfO0HadH7qGSnf35bX8VtASUuUq7ofu/rc+c7KZ2TwdO6R5g3hiExJq6MPuSGQ66F9j/zju2TJW07w+VOAk5cyu0nSfvgbjAE7c1O+sTggFvzzxBwklGqSZjTlzOi618iUEz73xgUI6PF9rDXT/IeBodiwmRyzvwltiuHxODodj8iBGu+xdtDyq67mJIJq4xCn2Fm8tikBivz1q4hpR6ZJOSgqzL0Efcd+UUKjBOGUZidQxYJBUKnvqUQYnDAhyS5YdXW6PrYfSw2IbuMY9AyvCg1MQNMCIxQ7fASwvSXz1IfTe6Fy8JKu5R3h1WfO5nkFAOONiHaaeZo2ok6zkNWT/Ln6/VjzIWUGJPOsLI6KpCmMzziEBIaHN8gA5BB+ItzI0G7Au1G4RYl56SaRfMMnH8WUWQQsjxTymNJ7156YcYxaCGSRTueoXRQUTjUQngKU3LwR4t4Yw5+RYJs5yCpohs4GYVMuuK9jHKPiIpJB3PSzohUvAvd/n8IGgEpRrhNQ3GmiTM9g6nVENUqYtUrUKUqQRl0hIFUJqGrkWCkSWykpcOHY7vmviXOOVIGUeJUROFBEiZCGeJ+p2VwWuHfUhmEjhbsG2S9hZ47yP2z30fPHUTWW8iWRkR6iW0/0g9MMk963qwP7bK6mBLP7YogJ/FFp1zoLMsyiNpNBKAOy5YYKXFh8AFEsRL+rQyOD2CQigiQKKxMHFqZKedpWByJ/UzCKhPfok2vpOqkBsoUYLpSRC5fhjO6FFMsoKslkALpm6RyhZ/TGdny+4xhZ+JIZPuEHJ77O/ySvV6IwFHh/WwMWLCkgBhxQ0oIgS5IjADH00l5T5NTGNMGPvfldnhIZZjTId1fb6STMyCMTtqNBFWUmLLoOucYpKdTNsgF3XqOGiKLpB+D48cLzdEUIsxJ8VfWmpAhhBXNuALliChHiaiyGURgkEqH+jLsMzm/XrFf4wzvwkuo5HurzrDK0tmEnaqzKRwUHpMdAyZon32EDsFJcowOdYWNDpbfCQwzpCAVVsJPrVpYvz2ycrx9SARaqmeeMoldy0tu5ppWDO21IRbpa4wQWXA6JGcHTQpEYUxUm1MLkjLcfSfUGhTwYYsIDALTrladvX2wIJWDOgAS8XuFiI7Hka00eF324zDqrJiDLunYSYyJi9/rEjnbHgIjsuEQ5xdJmJCTuMtnX4fNeGwsQzgZxyJjcESQ44WN9nFeUTl6QoCaJ19YWWKxm+PDYkicatrMyXMmlWhFL1C623qEEaSAhnywhyVJWAWWS2QrSDlJwAZSnvQDdL+2FlBERBjX+H5XT17usYCTU76t55k8uzbQh8yeGBzxttErs7/+mie8OpX7B83OkBxGWmxY/cqz2w/Te7UBrmm1Mg5YU+ogO2zRzV2YDcgcwIxVd4GYTgxOylAaGDGA4/NN3AH4ILuao3ss2d35I82OI4bJEGWbuCxnkv7BOlpg85l2dAzOtW3zwQbOfGIFbwB6W4HL0QUWFby03f8DoG58gkGqygwAAAAASUVORK5CYII=" id="image1e3120a578" transform="scale(1 -1) translate(0 -51.12)" x="302.482286" y="-148.916571" width="51.12" height="51.12"/> - + +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" id="image6e1531e8f6" transform="scale(1 -1) translate(0 -51.12)" x="363.702857" y="-148.916571" width="51.12" height="51.12"/> - + +iVBORw0KGgoAAAANSUhEUgAAAEcAAABHCAYAAABVsFofAAADFklEQVR4nO2bMW4TQRSGd9bjSAiJAoHENSgiUSLBERBVDkBJ4TvQRFyAEqWCM9AiIQpOQBCUFFYUR4BCvBSWnHlvPZ/fGHf5/2p35t8349/vfzPrXafTHw+GrkBfHE+6OiYpmfO+wlvFSdW+vohDvNUY16P48Wu81XmdO0n1mdNnuvGQOACJA8hfL++Yhj4t18eTbun5RZ8pVeY64vrrbAzbN+ZeXXMhzmRrnGJMc129T3CQOID87fJetbN3tvLpavrAgtaqZAcbw49PXLZOMX7Q/qvrhCokDkDiAPLpn/vVTr9ckl9LLvJaasV/cO11y4IXr5vKHIDEAeTvv+6aBtp5linp0/jN4dv18YvPR/UYDTvr8Y45xn398J3pm315Vo1BNlPmACQOQOIA0tMPL43pEnnQ1Jz43TXXsdgy2zJmtG768UdcnM0Nh8QB5J8Xt01DMtapX+jtRykftSr/vB63dcvcyI7KHIDEAUgcQD5f3DINxq/oa38e4zJvcOdValucehhb80ZbEKEKiQPIfy+m9V6fupSfZV+DHXcdb2Qr5MIYEEKZA5A4AIkDyP0C3sJxJhyi9SJam7ZwB6gro8qx7d5jzYM7fz0rj0PiACQOIOeFvw8Adun5ES9tPKQYXbehdtBcsD6VvHhdoTqqzAFIHECeNtgKl3KIwVuA2NhtcerEUQw91NsNEgcgcQB5eu5agjVg7N0G7g7jjeLsaW5lffLXKXMAEgeQD87hzrdlp7vzzroecy9WbeAODf8EuvGQOACJA8gHi/r7KS3e/fh+tj5+9PwYYrZs7ethiPvpZGa6Do+ON/K2xVTmACQOID1+8ir8lAuXxL6+07Qxbef+lnZ6qlfwKB1kqzgkDkDiAPL07Ldt4Zdi1ofR+uMxqg17q09FJ3zlLeMrcwASB5An8wvTMPD7tZuPidd1297ZjcXsnCVwSY7PjbYAyhyAxAFIHEAe5me2pY/WFacr1JUUrQE9f1cpWp/8XKLj65fAOCQOIF/N5/XeBlslXK4Lbov9yGZoKze3qAXdZ1LmACQOQOIA/gFzjLJEhG9/CQAAAABJRU5ErkJggg==" id="image71ded072da" transform="scale(1 -1) translate(0 -51.12)" x="57.6" y="-242.838454" width="51.12" height="51.12"/> - + +iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAABsklEQVR4nI2UMY7UQBBF36/uFUgzntEEJETADVaCG5CTbIiIOQCChCvABQgJCTfaEGlDAsQ1IAEJobFZN4Hb7mrbsyJozfevX69K457RU10kACSGT0NWNFlLAjOqrNngw5CTEcPhkB9OQEaAh1UQVX7UYU8yLcPSqp8kyOwqm3W8OWxmBcr0ESCWdQf2mdjt7+YAFSytQpga07QEJCt1a/eR+fl89YZ2F6bTNUW3TeC4C7Q74/ryNced0TbDOTYBPX7xLlXb3aLLdqeyIraNluGVxpP1GTx2zQrgP7erwcN1jt02LaZMDaPH7RDfF/9ulyaqhxTIzMflsxf77c0JSP2LRMld15KVzwIxbrocKACt6hlw9PI8U0JKxGb7BylhLmyuYQq7Rl83lzES8d7md9U4Dwy6r/xlPWHqAYj3Nz8XhQ9PPvLyy/PBo4RNiUDR788/8errBYEyUG+/PUtjYxgbvVZPcJt6DVSwQE98eOd7hvQEV5gDgnos+8GD6afhgUR8cPYjNxbTXCC4r6NAxiHegyCIj85+DQ/5LQZp+v8M+d2alLUwjJDvj2FYzgQNXf8AXD2vrUvE87wAAAAASUVORK5CYII=" id="image8c6ddab5e9" transform="matrix(2.550857 0 0 2.550857 118.820571 242.941311)" style="image-rendering:crisp-edges;image-rendering:pixelated" width="20" height="20"/> - + +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" id="imaged4b3b72061" transform="scale(1 -1) translate(0 -51.12)" x="180.041143" y="-242.838454" width="51.12" height="51.12"/> - + +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" id="image2636f74bf5" transform="scale(1 -1) translate(0 -51.12)" x="241.261714" y="-242.838454" width="51.12" height="51.12"/> - + +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" id="imagef343df5550" transform="scale(1 -1) translate(0 -51.12)" x="302.482286" y="-242.838454" width="51.12" height="51.12"/> - + +iVBORw0KGgoAAAANSUhEUgAAAEcAAABHCAYAAABVsFofAAAK10lEQVR4nOVcTYwcRxX+XnXNzHrX3rXXXv/FcTbYsYycWBgSHyKjWIpl4EBIBBw4IJNICIGEkOxcMCFBSmwioUQQCSSE4AQCCw5IcCCRUJQ4EKOIyA6KFVs2tjf+I7ter3e9szPT3VUcerqnuutVz/TuzNpWnjTydPWr97766r1XVb09posfrtUwxCNCVoTV0tQFo8v0d+o6LPMYeF3B2PXIobvuDNvuErqQIQcAPIfyx404Oj3WIofr5vHj4XV51TuWVFnT9pAEjGAyvnpkBRk82G2RDUO0bvZnVTMQm7p2U9MGb6RlIzSwGbqGDY5sjmg5qyXrjBu0px1EdEqazhAf6zL9XTaEAwNHPOcLADymmSNdVlWFNSBIpa4HqIEQhJqWGA8H8VEwiNVyGo/0fYSVGy5b/euX78VrcwMYDwYxIqcx4s1ggAIoEKxo1Q4iGNJ23HORxbtQOT221koyOavLlqIHnQrDEW8GI14DAPBufRjfP/4E1AdLIbbexKmvPMc6q6w/h2f/ehA331sJbJ7F4R1/xmeXXAEAXAs1xlW/4U9Z/bMYei1ceZGzzchx1Q6PFGpaop989FMJs6qC8MxSbHijgUu0NNfh1NlhbHzTx2UMYPyBZVgppuCRwLWwipoqOf1ZbYvAklleYn+SA5mqARoABpPLmiph6bZJnBsawvDd13Idbtx2Bef71mDk7gks82o43ggAANfCIVR1K52FGTk6xmCT1EupaZuHVM1JctxcHaDw9vRm/PPKKPzQw0+3H8HxLx5K7lPOrOo9rSL3/oX12Pf+PgDAztVj2DV4mu2T1LoUht5HToqH5mTJFGMOQG9c3IS+vwyhUtMY/fSNeTnfds9l6KdWRfYeK2HH1gstX2RHjglyMWRWVaxoldUwvVpx4VyvSwze1BDBwmZQ1nRir6oqbJQAfIHmVq5uSk2XLByyqso5MxddfG/76xh9cBwA8OvJh3HkZw9j5B1gYgegDzh2dgBGf/ETjBwTmHwAeHzPMbww9CsAwPnGCCbDAYYUbjnnCey2tBamFheyruxNYHaWvvvJ15Pv+w8/ifMH9+M8APwWAJ52OrzwnacRJ8+JXYfw0lt/bF6dwnPvPZbocdHq2sD1SurMwiTnwtY+J7vxi9rSIDe9chbFzraR/P2tHwB4JrmuqnKh9On16lVVNg9s5JgKWXn16s+7Aibr91bUGVOqqmxFq5wL0+HUIsV1xu6OxH65c5UrpVwT1g3h9nuyFsqm4xhQixRu5j636lt4deKXCwbT4Gqdk5TeR1A8WaYvWQQkAIx9cyvw4+LO937qWeC4DSbP32KmFVdeZD1oNZIDzONHv40+L9r6b2ycx9z9h6EnyxArG/hg1zps3XjF6nP90l3Y/I/n4Y8vAQZ96A3T2Nf/JICImFpQ6mgBaNfeLcmWF480ZEPxtcUE85+3N2PTH6YhqnU88qcT+NvuV5J7zuPDXYA2HiC9c34jnnniGwCAM18bwn07L6BdCue1d1tqYcnyJRthmhx2S0eA6o+YnfDzT+IuGQ+XIexvzU7dqnVp4VLNFdndkEbIpFWjTVoJ0lDrazj3pSUAlkBOr52X8yMTO/HfLy+JbK6vwg/bR2werm5LzSAnxiCDkH/IbAIaGZ7Bqg1XAQBjU8ux9YcvY8VphalNAvqw+/iw5fmXMHxSY2qTQOV/k9j+0FkAwIzfh5l6+kznmpjFknooLX/CDz2Yn0AJBEqk2gCgz/OxvDyHIPCw+l0fx44cwMgJP9fh8EmNf/3+ANb828dstYIV5TmsKM8BABqBBz8UyacReMknCKNPI5DJx8TSC/FDD/VApj4yCAQcD/ST2fRDgYm5qNaEocDU5hJ2P/oiJu/lnz/HMrNBYPeeF3HtvgqABsZmV0Tt9QoCZUcsB2Oxoieuval9juLSKlGI4M4EfbhZ7UtuTz9Yw40dBCHn8JkvHMRDa8bw6NBJjJYm8GEwjKPTW3D06ibUp2dx7n4JknMoS4VL14dYYFxKxRMWWnd6I1x5odHfHUqQsRHkmDlW1TXLOYPvxEYR3W7YiNukDlu9UmpkfUl17EyXQ9NNssnQdagugCipg0w4tRmQ7gAQChF4e5INABIB5VhzuModUObiDiZbQjEa7q0Lr8DNiHOg7v6WFW5QbQnsHtmSgixQzpjdpFOEtIsUB0i2vyv023W1bSyUbEmhG7nOCTuyZp/TdTnmfPF5v6hkZ0qDpMCtZOpqjvWOnHA2TN3bl+xWWrVxzBOV1c2vPW3JLjB4J44uki2dj2VzjGVv6axCB45vN7I5DFIE7ZW6M9DO+gO3hmyuS5RWLgwdMmw6njdRRvvtQDYASOJOdkUc54SzZeYOI1sKgxxnxx44LkR287ITsp04GLLdutE/qchpl1JOY2YX434vyD71wv4CRjuXLT962RpzZwUZsMCbetk6pwn2TBeaZb5LIbILCldeWpvAgmli3dKZe9RmMPNIyx5y4yAnpyAXLqiE1IGm45nn/PWSCUYER07qba151BkIAgSgRaRHGoAGSCF98uuwoBZekbok1jGKXJGTAWMtIMagtaehmiOn5j1SAIUaqd13s2/R1NHO5w7dFRHY2KyCXDR6YiK0aPVNoia7IZ1H9FBennZRKLTNt9LKSQrZt41iSwoQQfQyJWlAC4LyACUJ2jP0uBXNQshD6eUqFUvMg+lLcoXIVLJeFCBAec00UoCsKZSnQ5RmfIh6AFWW8AdLaAxJBH1RPUpSTccAyCI7H0PvhQJYkS2zr8+2BUQUPSshAikNr6FRmazDu3od+uYsvIF+iHXDUGWBsCygBSXRlbwDTu2ilbndY4acq5VZ9FhAZgEWGkIQFDRECIiGhqg2oKZu4LXp32Bv8BTE4ACE3xflMQEijHRJGaHLFNpbQUosrbRqOZQU8KljiY7adfMvOaSiyCGloT0C9fVhr/91UGUAWgiQatWhOHJiNxQva8gW3zYYeijxqm1ykUorZ5E0F4xmrdFeNFjSgOovw1u1HGLZAHSlBLU0em3V8zV0M2IoREIwYJDM+cvIYizn6f1e5E9S0NqMpCAIHpBukgPjBYugvwRV8qKfK3oE5QloAkRdt5ap7IYwm+M5pOT9+KRbIgITQ/PPwdbvGWKF0IyobE0y7glAlQhhRSZRRqqZTr6xC8wh2xIHKb1c0rnfdUjRHEARkKZoj6LXqynSomawUKCTAhwZsQteZJu74FO9pwdPZr8nKVSW49YA2u9OSREoJOhAGztkDajM6gREyz9HNPMWDLv57KGIkIkc8s2nXQ5AOctuou9Im3R/VwrbuouZUgAg/PSfYTRRuiCnxLFCdDSjcSTMk+y0rcWJIPJVChdBZyInucOBZ0DGJGT5FYyNomTHtpmo6oVQaJ+UJfwg3zFLSvPK9bjDfHOjCClm7VkkUhLXcZCY0fP5Txzo+NUnzdWVNqR2pOuqVwVssBtF13+Y0aHd9pFjAE82bl0gJEX0fOxm2pNvnN0C2FJnKwTMaxZOorg3TwsMiCO6qI12ZC/QrnlHat9nlezOAlaRcaQDFUqpBRLexGEfRxZOuNQN+y10dnDOumAPjq1NTmCOwsDYYHG57HKkOzE4yWl035lD1zk4ttAzpDt1O5+4ItHeIscwlDpAdwjc5dh0mloWi6QTo7tQorO6CbZ0kbf/t5499FXeWIG06Capad3FI5UlJ08+TsT9HymtNvzrv52RAAAAAElFTkSuQmCC" id="image76dd97cf22" transform="scale(1 -1) translate(0 -51.12)" x="363.702857" y="-242.838454" 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..7129fba6d5b4 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/log_scale_image.pdf b/lib/matplotlib/tests/baseline_images/test_image/log_scale_image.pdf index c26419850251..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 1df80c1b2045..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 259eb2c9c7f3..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 @@ -6,11 +6,11 @@ - 2025-05-14T18:02:41.587512 + 2025-07-10T19:29:53.473547 image/svg+xml - Matplotlib v3.11.0.dev832+gc5ea66e278, https://matplotlib.org/ + Matplotlib v3.11.0.dev1075+g945334b731, https://matplotlib.org/ @@ -39,7 +39,7 @@ z +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" id="image2e672a6aec" transform="scale(1 -1) translate(0 -345.6)" x="72" y="-43.2" width="446.4" height="345.6"/> - - + + + + + + 2025-10-08T04:52:19.019436 + image/svg+xml + + + Matplotlib v3.11.0.dev1425+gb39ccbe8f, 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_png/pngsuite.png b/lib/matplotlib/tests/baseline_images/test_png/pngsuite.png index e8ba8c51be42..8b567e0a0598 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/test_agg.py b/lib/matplotlib/tests/test_agg.py index 100f632a9306..69434fd393ec 100644 --- a/lib/matplotlib/tests/test_agg.py +++ b/lib/matplotlib/tests/test_agg.py @@ -374,7 +374,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_axes.py b/lib/matplotlib/tests/test_axes.py index 6e839ef2f189..7faaf7094e1e 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -239,8 +239,9 @@ def test_matshow(fig_test, fig_ref): ax_ref.xaxis.set_ticks_position('both') +# 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 if platform.machine() == 'x86_64' else 0.031) + tol=0.02 if platform.machine() == 'x86_64' else 0.04) def test_formatter_ticker(): import matplotlib.testing.jpl_units as units units.register() @@ -809,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() @@ -1516,7 +1518,8 @@ def test_pcolormesh_log_scale(fig_test, fig_ref): ax.set_xscale('log') -@image_comparison(['pcolormesh_datetime_axis.png'], style='mpl20') +# 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 @@ -1544,7 +1547,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) @@ -1576,6 +1580,9 @@ def test_pcolor_log_scale(fig_test, fig_ref): 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] @@ -2580,6 +2587,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"): @@ -2753,7 +2769,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), @@ -6133,6 +6150,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) @@ -6492,7 +6524,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' diff --git a/lib/matplotlib/tests/test_backend_svg.py b/lib/matplotlib/tests/test_backend_svg.py index 2c64b7c24b3e..7864b3bb68bd 100644 --- a/lib/matplotlib/tests/test_backend_svg.py +++ b/lib/matplotlib/tests/test_backend_svg.py @@ -74,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']) @@ -84,7 +85,8 @@ 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(extensions=['svg'], tol=20) diff --git a/lib/matplotlib/tests/test_backends_interactive.py b/lib/matplotlib/tests/test_backends_interactive.py index 6bc7de433825..101c1cb81cad 100644 --- a/lib/matplotlib/tests/test_backends_interactive.py +++ b/lib/matplotlib/tests/test_backends_interactive.py @@ -208,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. diff --git a/lib/matplotlib/tests/test_colorbar.py b/lib/matplotlib/tests/test_colorbar.py index 614c7ae5c20c..72e38d32e82f 100644 --- a/lib/matplotlib/tests/test_colorbar.py +++ b/lib/matplotlib/tests/test_colorbar.py @@ -152,6 +152,7 @@ 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', @@ -159,7 +160,7 @@ def test_colorbar_extension_inverted_axis(orientation, extend, expected): 'cbar_sharing', ], extensions=['png'], remove_text=True, - savefig_kwarg={'dpi': 40}) + 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 @@ -731,7 +732,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)) diff --git a/lib/matplotlib/tests/test_colors.py b/lib/matplotlib/tests/test_colors.py index a5c7a14bf9d5..985a7b024ed4 100644 --- a/lib/matplotlib/tests/test_colors.py +++ b/lib/matplotlib/tests/test_colors.py @@ -2132,30 +2132,30 @@ def test_colorizer_multinorm_implicit(): # test call with two single values data = [0.1, 0.2] - res = (0.10009765625, 0.1510859375, 0.20166015625, 1.0) + 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.10009766, 0.19998877, 0.29931641, 1.], - [0.20166016, 0.30098633, 0.40087891, 1.]] + 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.00244141, 0.50048437, 0.99853516, 1.], - [0.09228516, 0.50048437, 0.90869141, 1.], - [0.18212891, 0.50048437, 0.81884766, 1.], - [0.27197266, 0.50048437, 0.72900391, 1.]], - [[0.36572266, 0.50048437, 0.63525391, 1.], - [0.45556641, 0.50048438, 0.54541016, 1.], - [0.54541016, 0.50048438, 0.45556641, 1.], - [0.63525391, 0.50048437, 0.36572266, 1.]], - [[0.72900391, 0.50048437, 0.27197266, 1.], - [0.81884766, 0.50048437, 0.18212891, 1.], - [0.90869141, 0.50048437, 0.09228516, 1.], - [0.99853516, 0.50048437, 0.00244141, 1.]]]) + 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, " @@ -2196,7 +2196,7 @@ def test_colorizer_multinorm_explicit(): # test call with two single values data = [0.1, 0.2] - res = (0.100098, 0.375492, 0.650879, 1.) + res = (0.098039, 0.374510, 0.65098, 1.) assert_array_almost_equal(ca.to_rgba(data), res) diff --git a/lib/matplotlib/tests/test_contour.py b/lib/matplotlib/tests/test_contour.py index 87e2d0ce182e..f397aee2c9c4 100644 --- a/lib/matplotlib/tests/test_contour.py +++ b/lib/matplotlib/tests/test_contour.py @@ -127,8 +127,9 @@ def test_contour_manual_moveto(): assert clabels[0].get_text() == "0" +# TODO: tighten tolerance after baseline image is regenerated for text overhaul @image_comparison(['contour_disconnected_segments'], - remove_text=True, style='mpl20', extensions=['png']) + remove_text=True, style='mpl20', extensions=['png'], 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) @@ -229,7 +230,8 @@ def test_lognorm_levels(n_levels): assert len(visible_levels) <= n_levels + 1 -@image_comparison(['contour_datetime_axis.png'], style='mpl20') +# 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) diff --git a/lib/matplotlib/tests/test_dates.py b/lib/matplotlib/tests/test_dates.py index 23b35e78c1f5..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) @@ -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,7 +273,8 @@ 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() @@ -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 diff --git a/lib/matplotlib/tests/test_figure.py b/lib/matplotlib/tests/test_figure.py index e666a3b99f7f..95568d237b91 100644 --- a/lib/matplotlib/tests/test_figure.py +++ b/lib/matplotlib/tests/test_figure.py @@ -25,8 +25,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 +67,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']: @@ -320,7 +322,8 @@ def test_add_subplot_invalid(): fig.add_subplot(ax) -@image_comparison(['figure_suptitle.png']) +# 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') @@ -1396,8 +1399,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) diff --git a/lib/matplotlib/tests/test_image.py b/lib/matplotlib/tests/test_image.py index 9b598fbf7193..da7a198a2a94 100644 --- a/lib/matplotlib/tests/test_image.py +++ b/lib/matplotlib/tests/test_image.py @@ -1604,8 +1604,8 @@ def test_large_image(fig_test, fig_ref, dim, size, msg, origin): 'accurately displayed.'): fig_test.canvas.draw() - 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', @@ -1663,19 +1663,33 @@ def test__resample_valid_output(): [(np.array([[0.1, 0.3, 0.2]]), mimage.NEAREST, np.array([[0.1, 0.1, 0.1, 0.3, 0.3, 0.3, 0.3, 0.2, 0.2, 0.2]])), (np.array([[0.1, 0.3, 0.2]]), mimage.BILINEAR, - np.array([[0.1, 0.1, 0.15078125, 0.21096191, 0.27033691, - 0.28476562, 0.2546875, 0.22460938, 0.20002441, 0.20002441]])), + 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): - # Test that equivalent affine and nonaffine transforms resample the same + # 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) + assert_allclose(affine_result, expected, atol=atol) # Create a nonaffine version of the same transform # by compositing with a nonaffine identity transform @@ -1684,13 +1698,13 @@ class NonAffineIdentityTransform(Transform): output_dims = 2 def inverted(self): - return self + return self nonaffine_transform = NonAffineIdentityTransform() + affine_transform nonaffine_result = np.empty_like(expected) mimage.resample(data, nonaffine_result, nonaffine_transform, interpolation=interpolation) - assert_allclose(nonaffine_result, expected, atol=5e-3) + assert_allclose(nonaffine_result, expected, atol=atol) def test_axesimage_get_shape(): diff --git a/lib/matplotlib/tests/test_lines.py b/lib/matplotlib/tests/test_lines.py index fe92547c5963..8bf6fea2cdf7 100644 --- a/lib/matplotlib/tests/test_lines.py +++ b/lib/matplotlib/tests/test_lines.py @@ -33,7 +33,7 @@ def test_segment_hits(): # Runtimes on a loaded system are inherently flaky. Not so much that a rerun # won't help, hopefully. -@pytest.mark.flaky(reruns=3) +@pytest.mark.flaky(reruns=5) def test_invisible_Line_rendering(): """ GitHub issue #1256 identified a bug in Line.draw method diff --git a/lib/matplotlib/tests/test_marker.py b/lib/matplotlib/tests/test_marker.py index a1e71f1f6533..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]]), 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_pickle.py b/lib/matplotlib/tests/test_pickle.py index 82fc60e186c7..1590990cdeb0 100644 --- a/lib/matplotlib/tests/test_pickle.py +++ b/lib/matplotlib/tests/test_pickle.py @@ -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_polar.py b/lib/matplotlib/tests/test_polar.py index 4f9e63380490..4cbb099e3293 100644 --- a/lib/matplotlib/tests/test_polar.py +++ b/lib/matplotlib/tests/test_polar.py @@ -214,7 +214,8 @@ def test_polar_theta_position(): ax.set_theta_direction('clockwise') -@image_comparison(['polar_rlabel_position.png'], 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') @@ -229,7 +230,8 @@ def test_polar_title_position(): ax.set_title('foo') -@image_comparison(['polar_theta_wedge.png'], style='default') +# 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 diff --git a/lib/matplotlib/tests/test_rcparams.py b/lib/matplotlib/tests/test_rcparams.py index 2235f98b720f..eb9d3bc8866b 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, @@ -672,3 +673,21 @@ def test_rc_aliases(group, option, alias, value): 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) == set(mpl.rcParams.keys()) + + +def test_validators_defined_as_code(): + for param in rcsetup._params: + 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: + 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_skew.py b/lib/matplotlib/tests/test_skew.py index 8527e474fa21..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) diff --git a/lib/matplotlib/tests/test_text.py b/lib/matplotlib/tests/test_text.py index 97b37b91f697..551adbedbc61 100644 --- a/lib/matplotlib/tests/test_text.py +++ b/lib/matplotlib/tests/test_text.py @@ -139,7 +139,8 @@ def test_multiline(): ax.set_yticks([]) -@image_comparison(['multiline2'], style='mpl20') +# TODO: tighten tolerance after baseline image is regenerated for text overhaul +@image_comparison(['multiline2'], style='mpl20', tol=0.05) def test_multiline2(): # Remove this line when this test image is regenerated. plt.rcParams['text.kerning_factor'] = 6 @@ -210,7 +211,8 @@ def test_antialiasing(): mpl.rcParams['text.antialiased'] = False # Should not affect existing text. -@image_comparison(['text_contains.png']) +# TODO: tighten tolerance after baseline image is regenerated for text overhaul +@image_comparison(['text_contains.png'], tol=0.05) def test_contains(): fig = plt.figure() ax = plt.axes() @@ -279,7 +281,8 @@ def test_titles(): ax.set_yticks([]) -@image_comparison(['text_alignment'], style='mpl20') +# TODO: tighten tolerance after baseline image is regenerated for text overhaul +@image_comparison(['text_alignment'], style='mpl20', tol=0.08) def test_alignment(): plt.figure() ax = plt.subplot(1, 1, 1) @@ -1132,8 +1135,9 @@ def test_empty_annotation_get_window_extent(): assert points[0, 1] == 50.0 +# TODO: tighten tolerance after baseline image is regenerated for text overhaul @image_comparison(baseline_images=['basictext_wrap'], - extensions=['png']) + extensions=['png'], tol=0.3) def test_basic_wrap(): fig = plt.figure() plt.axis([0, 10, 0, 10]) @@ -1149,8 +1153,9 @@ def test_basic_wrap(): plt.text(-1, 0, t, ha='left', rotation=-15, wrap=True) +# TODO: tighten tolerance after baseline image is regenerated for text overhaul @image_comparison(baseline_images=['fonttext_wrap'], - extensions=['png']) + extensions=['png'], tol=0.3) def test_font_wrap(): fig = plt.figure() plt.axis([0, 10, 0, 10]) @@ -1182,8 +1187,9 @@ def test_va_for_angle(): assert alignment in ['center', 'top', 'baseline'] +# TODO: tighten tolerance after baseline image is regenerated for text overhaul @image_comparison(baseline_images=['xtick_rotation_mode'], - remove_text=False, extensions=['png'], style='mpl20') + remove_text=False, extensions=['png'], style='mpl20', tol=0.3) def test_xtick_rotation_mode(): fig, ax = plt.subplots(figsize=(12, 1)) ax.set_yticks([]) @@ -1202,8 +1208,9 @@ def test_xtick_rotation_mode(): 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(baseline_images=['ytick_rotation_mode'], - remove_text=False, extensions=['png'], style='mpl20') + remove_text=False, extensions=['png'], style='mpl20', tol=0.3) def test_ytick_rotation_mode(): fig, ax = plt.subplots(figsize=(1, 12)) ax.set_xticks([]) diff --git a/lib/matplotlib/tests/test_ticker.py b/lib/matplotlib/tests/test_ticker.py index a9104cc1b839..c3c53ebaea73 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 @@ -1935,7 +1935,10 @@ def test_bad_locator_subs(sub): @mpl.style.context('default') def test_small_range_loglocator(numticks, lims, ticks): ll = mticker.LogLocator(numticks=numticks) - assert_array_equal(ll.tick_values(*lims), ticks) + 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') diff --git a/lib/matplotlib/tests/test_transforms.py b/lib/matplotlib/tests/test_transforms.py index 59a765107d7b..2b4351a5cfbb 100644 --- a/lib/matplotlib/tests/test_transforms.py +++ b/lib/matplotlib/tests/test_transforms.py @@ -977,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) @@ -1093,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_units.py b/lib/matplotlib/tests/test_units.py index d2350667e94f..c13c54a101fc 100644 --- a/lib/matplotlib/tests/test_units.py +++ b/lib/matplotlib/tests/test_units.py @@ -80,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 @@ -142,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() diff --git a/lib/matplotlib/tests/test_usetex.py b/lib/matplotlib/tests/test_usetex.py index cd9f2597361b..78d9fd6cc948 100644 --- a/lib/matplotlib/tests/test_usetex.py +++ b/lib/matplotlib/tests/test_usetex.py @@ -226,8 +226,9 @@ def test_pdf_type1_font_subsetting(): _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 diff --git a/lib/matplotlib/ticker.py b/lib/matplotlib/ticker.py index f82eeedc8918..e27d71974471 100644 --- a/lib/matplotlib/ticker.py +++ b/lib/matplotlib/ticker.py @@ -1029,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) @@ -1730,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): """ @@ -1738,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): @@ -1881,7 +1881,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)] @@ -1910,7 +1910,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): @@ -1980,7 +1980,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): @@ -2236,7 +2236,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) @@ -2254,7 +2254,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': @@ -2718,7 +2718,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): diff --git a/lib/matplotlib/transforms.py b/lib/matplotlib/transforms.py index 6ffc82edac3e..44d01926f2e8 100644 --- a/lib/matplotlib/transforms.py +++ b/lib/matplotlib/transforms.py @@ -44,7 +44,7 @@ import numpy as np from numpy.linalg import inv -from matplotlib import _api +from matplotlib import _api, _docstring from matplotlib._path import affine_transform, count_bboxes_overlapping_bbox from .path import Path @@ -2865,7 +2865,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. @@ -2923,7 +2923,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. @@ -2945,6 +2951,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 @@ -2974,7 +2986,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. @@ -2994,6 +3006,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 da73a8127cc3..ebee3954a3a7 100644 --- a/lib/matplotlib/transforms.pyi +++ b/lib/matplotlib/transforms.pyi @@ -316,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, @@ -323,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/widgets.py b/lib/matplotlib/widgets.py index db6defcabcad..cfe8cdc7d9f5 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 @@ -118,6 +116,11 @@ class AxesWidget(Widget): def __init__(self, ax): self.ax = ax self._cids = [] + self._blit_background_id = None + + def __del__(self): + if self._blit_background_id is not None: + self.canvas._release_blit_background_id(self._blit_background_id) canvas = property( lambda self: getattr(self.ax.get_figure(root=True), 'canvas', None) @@ -146,6 +149,26 @@ 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: + self._blit_background_id = self.canvas._get_blit_background_id() + 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): """ @@ -223,7 +246,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"]) @@ -260,7 +283,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: @@ -1083,8 +1106,7 @@ def __init__(self, ax, labels, actives=None, *, useblit=True, if actives is None: actives = [False] * len(labels) - self._useblit = useblit and self.canvas.supports_blit - self._background = None + self._useblit = useblit ys = np.linspace(1, 0, len(labels)+2)[1:-1] @@ -1112,7 +1134,10 @@ def __init__(self, ax, labels, actives=None, *, useblit=True, **cbook.normalize_kwargs(check_props, collections.PathCollection), 'marker': 'x', 'transform': ax.transAxes, - 'animated': self._useblit, + '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) @@ -1131,7 +1156,8 @@ 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) + 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._checks) @_call_with_reparented_event @@ -1236,9 +1262,10 @@ def set_active(self, index, state=None): self._checks.set_facecolor(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._checks) self.canvas.blit(self.ax.bbox) else: @@ -1676,8 +1703,7 @@ def __init__(self, ax, labels, active=0, activecolor=None, *, ys = np.linspace(1, 0, len(labels) + 2)[1:-1] - self._useblit = useblit and self.canvas.supports_blit - self._background = None + self._useblit = useblit label_props = _expand_text_props(label_props) self.labels = [ @@ -1692,7 +1718,11 @@ def __init__(self, ax, labels, active=0, activecolor=None, *, **radio_props, 'marker': 'o', 'transform': ax.transAxes, - 'animated': self._useblit, + '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', @@ -1719,7 +1749,8 @@ 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) + 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) @_call_with_reparented_event @@ -1812,9 +1843,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: @@ -1963,14 +1995,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): @@ -1978,7 +2009,7 @@ 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): @@ -2003,8 +2034,9 @@ def onmove(self, event): 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) @@ -2093,6 +2125,7 @@ def __init__(self, *args, 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 @@ -2167,6 +2200,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): @@ -2177,7 +2220,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', @@ -2186,8 +2229,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: @@ -2203,6 +2244,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: @@ -2243,7 +2289,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) @@ -2290,8 +2336,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 @@ -2467,7 +2514,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'): @@ -2491,13 +2538,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 ------ @@ -2511,13 +2560,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 ------ @@ -2629,7 +2680,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 @@ -2695,7 +2753,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): @@ -3140,7 +3198,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 @@ -3269,7 +3327,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) @@ -3294,18 +3352,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 @@ -3812,7 +3870,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) @@ -3937,7 +3995,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) @@ -3946,7 +4004,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 @@ -3966,7 +4024,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, @@ -4248,7 +4306,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) diff --git a/lib/matplotlib/widgets.pyi b/lib/matplotlib/widgets.pyi index f74b9c7f32bf..2f34255d625c 100644 --- a/lib/matplotlib/widgets.pyi +++ b/lib/matplotlib/widgets.pyi @@ -35,6 +35,7 @@ class Widget: class AxesWidget(Widget): ax: Axes def __init__(self, ax: Axes) -> None: ... + def __del__(self) -> None: ... @property def canvas(self) -> FigureCanvasBase | None: ... def connect_event(self, event: Event, callback: Callable) -> None: ... @@ -272,7 +273,7 @@ class MultiCursor(Widget): class _SelectorWidget(AxesWidget): onselect: Callable[[float, float], Any] - useblit: bool + _useblit: bool background: Any validButtons: list[MouseButton] def __init__( @@ -284,6 +285,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: ... 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..17a1460f6be4 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/axisartist/axislines.py b/lib/mpl_toolkits/axisartist/axislines.py index c921ea597cb4..e0057d4f6c1e 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". diff --git a/lib/mpl_toolkits/axisartist/tests/test_axis_artist.py b/lib/mpl_toolkits/axisartist/tests/test_axis_artist.py index d44a61b6dd4a..96d8a2cde0f3 100644 --- a/lib/mpl_toolkits/axisartist/tests/test_axis_artist.py +++ b/lib/mpl_toolkits/axisartist/tests/test_axis_artist.py @@ -24,7 +24,8 @@ def test_ticks(): 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 diff --git a/lib/mpl_toolkits/axisartist/tests/test_axislines.py b/lib/mpl_toolkits/axisartist/tests/test_axislines.py index a1485d4f436b..a47ab2ea8a31 100644 --- a/lib/mpl_toolkits/axisartist/tests/test_axislines.py +++ b/lib/mpl_toolkits/axisartist/tests/test_axislines.py @@ -7,7 +7,8 @@ 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 @@ -28,7 +29,8 @@ 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 @@ -130,7 +132,8 @@ def test_axisline_style_tight(): ax.axis[direction].set_visible(False) -@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(): fig = plt.figure() ax = fig.add_subplot(111, axes_class=SubplotZero) 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 7d6554782fe6..ac31b8b30c97 100644 --- a/lib/mpl_toolkits/axisartist/tests/test_grid_helper_curvelinear.py +++ b/lib/mpl_toolkits/axisartist/tests/test_grid_helper_curvelinear.py @@ -76,7 +76,8 @@ 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(): fig = plt.figure(figsize=(5, 5)) @@ -136,7 +137,7 @@ 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 diff --git a/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py b/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py index 546659d05177..e9809ce2a106 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 @@ -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') @@ -645,7 +647,8 @@ def test_surface3d(): 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") @@ -741,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') @@ -1120,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)) diff --git a/meson.build b/meson.build index 54249473fe8e..47244656705f 100644 --- a/meson.build +++ b/meson.build @@ -31,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/src/_image_resample.h b/src/_image_resample.h index 6b325c8aa14b..1b7af133de31 100644 --- a/src/_image_resample.h +++ b/src/_image_resample.h @@ -569,23 +569,28 @@ 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); + *x = int(coord[0] * agg::image_subpixel_scale + offset); + *y = int(coord[1] * agg::image_subpixel_scale + offset); } } } @@ -596,6 +601,7 @@ class lookup_distortion int m_in_height; int m_out_width; int m_out_height; + bool m_edge_aligned_subpixels; }; @@ -781,7 +787,7 @@ void resample( 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_conv_t span_conv(span_gen, conv_alpha); @@ -806,7 +812,7 @@ void resample( 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_conv_t span_conv(span_gen, conv_alpha); diff --git a/src/_image_wrapper.cpp b/src/_image_wrapper.cpp index 6528c4a9270c..c062ef14a8f1 100644 --- a/src/_image_wrapper.cpp +++ b/src/_image_wrapper.cpp @@ -167,12 +167,17 @@ image_resample(py::array input_array, if (is_affine) { convert_trans_affine(transform, params.affine); - params.is_affine = true; - } else { + // If affine parameters will make subpixels visible, treat as nonaffine instead + if (params.affine.sx >= agg::image_subpixel_scale / 2 || params.affine.sy >= agg::image_subpixel_scale / 2) { + is_affine = false; + params.affine = agg::trans_affine(); // reset to identity affine parameters + } + } + if (!is_affine) { transform_mesh = _get_transform_mesh(transform, output_array.shape()); params.transform_mesh = transform_mesh.data(); - params.is_affine = false; } + params.is_affine = is_affine; } if (auto resampler =