diff --git a/.appveyor.yml b/.appveyor.yml
index a637fe545466..c3fcb0ea9591 100644
--- a/.appveyor.yml
+++ b/.appveyor.yml
@@ -17,7 +17,7 @@ skip_commits:
clone_depth: 50
-image: Visual Studio 2019
+image: Visual Studio 2022
environment:
diff --git a/.github/workflows/cibuildwheel.yml b/.github/workflows/cibuildwheel.yml
index a05d3ccc330c..9d4de069b078 100644
--- a/.github/workflows/cibuildwheel.yml
+++ b/.github/workflows/cibuildwheel.yml
@@ -41,7 +41,7 @@ jobs:
SDIST_NAME: ${{ steps.sdist.outputs.SDIST_NAME }}
steps:
- - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
+ - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
fetch-depth: 0
persist-credentials: false
@@ -127,7 +127,9 @@ jobs:
- os: ubuntu-24.04-arm
cibw_archs: "aarch64"
- os: windows-latest
- cibw_archs: "auto64"
+ cibw_archs: "AMD64"
+ - os: windows-11-arm
+ cibw_archs: "ARM64"
- os: macos-13
cibw_archs: "x86_64"
- os: macos-14
@@ -135,24 +137,32 @@ jobs:
steps:
- name: Download sdist
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
+ uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
with:
name: cibw-sdist
path: dist/
+ - name: Build wheels for CPython 3.14
+ uses: pypa/cibuildwheel@352e01339f0a173aa2a3eb57f01492e341e83865 # v3.1.3
+ with:
+ package-dir: dist/${{ needs.build_sdist.outputs.SDIST_NAME }}
+ env:
+ CIBW_BUILD: "cp314-* cp314t-*"
+ CIBW_ENABLE: "cpython-freethreading cpython-prerelease"
+ CIBW_ARCHS: ${{ matrix.cibw_archs }}
+ CIBW_MANYLINUX_X86_64_IMAGE: manylinux_2_28
+
- name: Build wheels for CPython 3.13
- uses: pypa/cibuildwheel@faf86a6ed7efa889faf6996aa23820831055001a # v2.23.3
+ uses: pypa/cibuildwheel@352e01339f0a173aa2a3eb57f01492e341e83865 # v3.1.3
with:
package-dir: dist/${{ needs.build_sdist.outputs.SDIST_NAME }}
env:
CIBW_BUILD: "cp313-* cp313t-*"
CIBW_ENABLE: cpython-freethreading
- # No free-threading wheels available for aarch64 on Pillow.
- CIBW_TEST_SKIP: "cp313t-manylinux_aarch64"
CIBW_ARCHS: ${{ matrix.cibw_archs }}
- name: Build wheels for CPython 3.12
- uses: pypa/cibuildwheel@faf86a6ed7efa889faf6996aa23820831055001a # v2.23.3
+ uses: pypa/cibuildwheel@352e01339f0a173aa2a3eb57f01492e341e83865 # v3.1.3
with:
package-dir: dist/${{ needs.build_sdist.outputs.SDIST_NAME }}
env:
@@ -160,25 +170,22 @@ jobs:
CIBW_ARCHS: ${{ matrix.cibw_archs }}
- name: Build wheels for CPython 3.11
- uses: pypa/cibuildwheel@faf86a6ed7efa889faf6996aa23820831055001a # v2.23.3
+ uses: pypa/cibuildwheel@352e01339f0a173aa2a3eb57f01492e341e83865 # v3.1.3
with:
package-dir: dist/${{ needs.build_sdist.outputs.SDIST_NAME }}
env:
CIBW_BUILD: "cp311-*"
CIBW_ARCHS: ${{ matrix.cibw_archs }}
-
- name: Build wheels for PyPy
- uses: pypa/cibuildwheel@faf86a6ed7efa889faf6996aa23820831055001a # v2.23.3
+ uses: pypa/cibuildwheel@352e01339f0a173aa2a3eb57f01492e341e83865 # v3.1.3
with:
package-dir: dist/${{ needs.build_sdist.outputs.SDIST_NAME }}
env:
CIBW_BUILD: "pp311-*"
CIBW_ARCHS: ${{ matrix.cibw_archs }}
CIBW_ENABLE: pypy
- # No wheels available for Pillow with pp311 yet.
- CIBW_TEST_SKIP: "pp311*"
- if: matrix.cibw_archs != 'aarch64' && matrix.os != 'windows-latest'
+ if: matrix.cibw_archs != 'aarch64' && matrix.os != 'windows-latest' && matrix.os != 'windows-11-arm'
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
@@ -198,7 +205,7 @@ jobs:
contents: read
steps:
- name: Download packages
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
+ uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
with:
pattern: cibw-*
path: dist
@@ -208,7 +215,7 @@ jobs:
run: ls dist
- name: Generate artifact attestation for sdist and wheel
- uses: actions/attest-build-provenance@db473fddc028af60658334401dc6fa3ffd8669fd # v2.3.0
+ uses: actions/attest-build-provenance@e8998f949152b193b063cb0ec769d69d929409be # v2.4.0
with:
subject-path: dist/matplotlib-*
diff --git a/.github/workflows/circleci.yml b/.github/workflows/circleci.yml
index d61db3f14345..3838a38004e0 100644
--- a/.github/workflows/circleci.yml
+++ b/.github/workflows/circleci.yml
@@ -11,7 +11,7 @@ jobs:
steps:
- name: GitHub Action step
uses:
- scientific-python/circleci-artifacts-redirector-action@7eafdb60666f57706a5525a2f5eb76224dc8779b # v1.1.0
+ scientific-python/circleci-artifacts-redirector-action@839631420e45a08af893032e5a5e8843bf47e8ff # v1.2.0
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
api-token: ${{ secrets.CIRCLECI_TOKEN }}
@@ -28,7 +28,7 @@ jobs:
runs-on: ubuntu-latest
name: Post warnings/errors as review
steps:
- - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
+ - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
persist-credentials: false
diff --git a/.github/workflows/clean_pr.yml b/.github/workflows/clean_pr.yml
index fc9021c920c0..fdfc446af15b 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@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
+ - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
fetch-depth: '0'
persist-credentials: false
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
index 3f71e1369834..eebdd65105e3 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@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
+ uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
persist-credentials: false
- name: Initialize CodeQL
- uses: github/codeql-action/init@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18
+ uses: github/codeql-action/init@96f518a34f7a870018057716cc4d7a5c014bd61c # v3.29.10
with:
languages: ${{ matrix.language }}
@@ -43,4 +43,4 @@ jobs:
pip install --user -v .
- name: Perform CodeQL Analysis
- uses: github/codeql-action/analyze@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18
+ uses: github/codeql-action/analyze@96f518a34f7a870018057716cc4d7a5c014bd61c # v3.29.10
diff --git a/.github/workflows/cygwin.yml b/.github/workflows/cygwin.yml
index 4a5b79c0538e..071368531d3f 100644
--- a/.github/workflows/cygwin.yml
+++ b/.github/workflows/cygwin.yml
@@ -79,12 +79,12 @@ jobs:
- name: Fix line endings
run: git config --global core.autocrlf input
- - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
+ - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
fetch-depth: 0
persist-credentials: false
- - uses: cygwin/cygwin-install-action@f61179d72284ceddc397ed07ddb444d82bf9e559 # v5
+ - uses: cygwin/cygwin-install-action@f2009323764960f80959895c7bc3bb30210afe4d # v6
with:
packages: >-
ccache gcc-g++ gdb git graphviz libcairo-devel libffi-devel
@@ -140,21 +140,21 @@ jobs:
# FreeType build fails with bash, succeeds with dash
- name: Cache pip
- uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
+ uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4
with:
path: C:\cygwin\home\runneradmin\.cache\pip
key: Cygwin-py3.${{ matrix.python-minor-version }}-pip-${{ hashFiles('requirements/*/*.txt') }}
restore-keys: ${{ matrix.os }}-py3.${{ matrix.python-minor-version }}-pip-
- name: Cache ccache
- uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
+ uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4
with:
path: C:\cygwin\home\runneradmin\.ccache
key: Cygwin-py3.${{ matrix.python-minor-version }}-ccache-${{ hashFiles('src/*') }}
restore-keys: Cygwin-py3.${{ matrix.python-minor-version }}-ccache-
- name: Cache Matplotlib
- uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
+ uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4
with:
path: |
C:\cygwin\home\runneradmin\.cache\matplotlib
diff --git a/.github/workflows/reviewdog.yml b/.github/workflows/linting.yml
similarity index 87%
rename from .github/workflows/reviewdog.yml
rename to .github/workflows/linting.yml
index bfad14923b82..f5cada1f3f9d 100644
--- a/.github/workflows/reviewdog.yml
+++ b/.github/workflows/linting.yml
@@ -10,9 +10,10 @@ jobs:
name: precommit
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
+ - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
fetch-depth: 0
+ persist-credentials: false
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
with:
python-version: "3.x"
@@ -26,7 +27,7 @@ jobs:
permissions:
checks: write
steps:
- - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
+ - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
persist-credentials: false
@@ -55,7 +56,7 @@ jobs:
permissions:
checks: write
steps:
- - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
+ - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
persist-credentials: false
@@ -86,7 +87,7 @@ jobs:
permissions:
checks: write
steps:
- - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
+ - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
persist-credentials: false
diff --git a/.github/workflows/mypy-stubtest.yml b/.github/workflows/mypy-stubtest.yml
index 92a67236fb9d..b40909b371a6 100644
--- a/.github/workflows/mypy-stubtest.yml
+++ b/.github/workflows/mypy-stubtest.yml
@@ -12,7 +12,7 @@ jobs:
permissions:
checks: write
steps:
- - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
+ - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
persist-credentials: false
diff --git a/.github/workflows/pr_welcome.yml b/.github/workflows/pr_welcome.yml
index 3bb172ca70e7..874f8807b478 100644
--- a/.github/workflows/pr_welcome.yml
+++ b/.github/workflows/pr_welcome.yml
@@ -9,10 +9,10 @@ jobs:
permissions:
pull-requests: write
steps:
- - uses: actions/first-interaction@34f15e814fe48ac9312ccf29db4e74fa767cbab7 # v1.3.0
+ - uses: actions/first-interaction@753c925c8d1ac6fede23781875376600628d9b5d # v3.0.0
with:
- repo-token: ${{ secrets.GITHUB_TOKEN }}
- pr-message: >+
+ repo_token: ${{ secrets.GITHUB_TOKEN }}
+ pr_message: >+
Thank you for opening your first PR into Matplotlib!
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index 2a48276707ce..e965819628be 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -90,7 +90,7 @@ jobs:
pygobject-ver: '<3.52.0'
steps:
- - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
+ - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
fetch-depth: 0
persist-credentials: false
@@ -179,7 +179,7 @@ jobs:
esac
- name: Cache pip
- uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
+ uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4
if: startsWith(runner.os, 'Linux')
with:
path: ~/.cache/pip
@@ -187,7 +187,7 @@ jobs:
restore-keys: |
${{ matrix.os }}-py${{ matrix.python-version }}-pip-
- name: Cache pip
- uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
+ uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4
if: startsWith(runner.os, 'macOS')
with:
path: ~/Library/Caches/pip
@@ -195,7 +195,7 @@ jobs:
restore-keys: |
${{ matrix.os }}-py${{ matrix.python-version }}-pip-
- name: Cache ccache
- uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
+ uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4
with:
path: |
~/.ccache
@@ -203,7 +203,7 @@ jobs:
restore-keys: |
${{ matrix.os }}-py${{ matrix.python-version }}-ccache-
- name: Cache Matplotlib
- uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
+ uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4
with:
path: |
~/.cache/matplotlib
@@ -344,32 +344,29 @@ jobs:
- name: Cleanup non-failed image files
if: failure()
run: |
- function remove_files() {
- local extension=$1
- find ./result_images -type f -name "*-expected*.$extension" | while read file; do
- if [[ $file == *"-expected_pdf"* ]]; then
- base=${file%-expected_pdf.$extension}_pdf
- elif [[ $file == *"-expected_eps"* ]]; then
- base=${file%-expected_eps.$extension}_eps
- elif [[ $file == *"-expected_svg"* ]]; then
- base=${file%-expected_svg.$extension}_svg
- else
- base=${file%-expected.$extension}
- fi
- if [[ ! -e "${base}-failed-diff.$extension" ]]; then
- if [[ -e "$file" ]]; then
- rm "$file"
- echo "Removed $file"
- fi
- if [[ -e "${base}.$extension" ]]; then
- rm "${base}.$extension"
- echo " Removed ${base}.$extension"
- fi
- fi
+ find ./result_images -name "*-expected*.png" | while read file; do
+ if [[ $file == *-expected_???.png ]]; then
+ extension=${file: -7:3}
+ base=${file%*-expected_$extension.png}_$extension
+ else
+ extension="png"
+ base=${file%-expected.png}
+ fi
+ if [[ ! -e ${base}-failed-diff.png ]]; then
+ indent=""
+ list=($file $base.png)
+ if [[ $extension != "png" ]]; then
+ list+=(${base%_$extension}-expected.$extension ${base%_$extension}.$extension)
+ fi
+ for to_remove in "${list[@]}"; do
+ if [[ -e $to_remove ]]; then
+ rm $to_remove
+ echo "${indent}Removed $to_remove"
+ fi
+ indent+=" "
done
- }
-
- remove_files "png"; remove_files "svg"; remove_files "pdf"; remove_files "eps";
+ fi
+ done
if [ "$(find ./result_images -mindepth 1 -type d)" ]; then
find ./result_images/* -type d -empty -delete
diff --git a/.gitignore b/.gitignore
index 1d30ba69aeaa..9389a1612b14 100644
--- a/.gitignore
+++ b/.gitignore
@@ -44,6 +44,7 @@ pip-wheel-metadata/*
.tox
# build subproject files
subprojects/*/
+subprojects/.*
!subprojects/packagefiles/
# OS generated files #
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 86a9a0f45440..11499188509e 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -8,7 +8,7 @@ exclude: |
LICENSE|
lib/matplotlib/mpl-data|
doc/devel/gitwash|
- doc/users/prev|
+ doc/release/prev|
doc/api/prev|
lib/matplotlib/tests/data/tinypages
)
diff --git a/README.md b/README.md
index 7b9c99597c0d..e7dce2a5f472 100644
--- a/README.md
+++ b/README.md
@@ -2,6 +2,7 @@
[](https://anaconda.org/conda-forge/matplotlib)
[](https://pypi.org/project/matplotlib)
[](https://numfocus.org)
+[](https://insights.linuxfoundation.org/project/matplotlib)
[](https://discourse.matplotlib.org)
[](https://gitter.im/matplotlib/matplotlib)
diff --git a/ci/codespell-ignore-words.txt b/ci/codespell-ignore-words.txt
index 0ebc5211b80c..e138f26e216a 100644
--- a/ci/codespell-ignore-words.txt
+++ b/ci/codespell-ignore-words.txt
@@ -20,3 +20,4 @@ whis
wit
Copin
socio-economic
+Ines
diff --git a/ci/schemas/conda-environment.json b/ci/schemas/conda-environment.json
index 458676942a44..fb1e821778c3 100644
--- a/ci/schemas/conda-environment.json
+++ b/ci/schemas/conda-environment.json
@@ -1,6 +1,6 @@
{
"title": "conda environment file",
- "description": "Support for conda's enviroment.yml files (e.g. `conda env export > environment.yml`)",
+ "description": "Support for conda's environment.yml files (e.g. `conda env export > environment.yml`)",
"id": "https://raw.githubusercontent.com/Microsoft/vscode-python/main/schemas/conda-environment.json",
"$schema": "http://json-schema.org/draft-04/schema#",
"definitions": {
diff --git a/doc/_embedded_plots/figure_subplots_adjust.py b/doc/_embedded_plots/figure_subplots_adjust.py
index 6f99a3febcdc..d32a029fe05d 100644
--- a/doc/_embedded_plots/figure_subplots_adjust.py
+++ b/doc/_embedded_plots/figure_subplots_adjust.py
@@ -7,8 +7,8 @@
overlay = fig.add_axes([0, 0, 1, 1], zorder=100)
overlay.axis("off")
-xycoords='figure fraction'
-arrowprops=dict(arrowstyle="<->", shrinkA=0, shrinkB=0)
+xycoords = 'figure fraction'
+arrowprops = dict(arrowstyle="<->", shrinkA=0, shrinkB=0)
for ax in axs.flat:
ax.set(xticks=[], yticks=[])
diff --git a/doc/_static/switcher.json b/doc/_static/switcher.json
index 62c8ed756824..c71f2c8c4859 100644
--- a/doc/_static/switcher.json
+++ b/doc/_static/switcher.json
@@ -1,7 +1,7 @@
[
{
"name": "3.10 (stable)",
- "version": "3.10.3",
+ "version": "3.10.6",
"url": "https://matplotlib.org/stable/",
"preferred": true
},
diff --git a/doc/_static/zenodo_cache/14940554.svg b/doc/_static/zenodo_cache/14940554.svg
new file mode 100644
index 000000000000..6e7d5c37bf7b
--- /dev/null
+++ b/doc/_static/zenodo_cache/14940554.svg
@@ -0,0 +1,35 @@
+
\ No newline at end of file
diff --git a/doc/_static/zenodo_cache/15375714.svg b/doc/_static/zenodo_cache/15375714.svg
new file mode 100644
index 000000000000..d5e403138561
--- /dev/null
+++ b/doc/_static/zenodo_cache/15375714.svg
@@ -0,0 +1,35 @@
+
\ No newline at end of file
diff --git a/doc/_static/zenodo_cache/16644850.svg b/doc/_static/zenodo_cache/16644850.svg
new file mode 100644
index 000000000000..89910032da4e
--- /dev/null
+++ b/doc/_static/zenodo_cache/16644850.svg
@@ -0,0 +1,35 @@
+
\ No newline at end of file
diff --git a/doc/_static/zenodo_cache/16999430.svg b/doc/_static/zenodo_cache/16999430.svg
new file mode 100644
index 000000000000..44c448643e91
--- /dev/null
+++ b/doc/_static/zenodo_cache/16999430.svg
@@ -0,0 +1,35 @@
+
\ No newline at end of file
diff --git a/doc/api/colors_api.rst b/doc/api/colors_api.rst
index 6b02f723d74d..18e7c43932a9 100644
--- a/doc/api/colors_api.rst
+++ b/doc/api/colors_api.rst
@@ -21,6 +21,7 @@ Color norms
:toctree: _as_gen/
:template: autosummary.rst
+ Norm
Normalize
NoNorm
AsinhNorm
@@ -31,6 +32,7 @@ Color norms
PowerNorm
SymLogNorm
TwoSlopeNorm
+ MultiNorm
Univariate Colormaps
--------------------
diff --git a/doc/api/next_api_changes/behavior/29958-TH.rst b/doc/api/next_api_changes/behavior/29958-TH.rst
new file mode 100644
index 000000000000..cacaf2bac612
--- /dev/null
+++ b/doc/api/next_api_changes/behavior/29958-TH.rst
@@ -0,0 +1,8 @@
+``Axes.add_collection(..., autolim=True)`` updates view limits
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+``Axes.add_collection(..., autolim=True)`` has so far only updated the data limits.
+Users needed to additionally call `.Axes.autoscale_view` to update the view limits.
+View limits are now updated as well if ``autolim=True``, using a lazy internal
+update mechanism, so that the costs only apply once also if you add multiple
+collections.
diff --git a/doc/api/next_api_changes/behavior/30272-ES.rst b/doc/api/next_api_changes/behavior/30272-ES.rst
new file mode 100644
index 000000000000..5a03f9bc7972
--- /dev/null
+++ b/doc/api/next_api_changes/behavior/30272-ES.rst
@@ -0,0 +1,2 @@
+``font_manager.findfont`` logs if selected font weight does not match requested
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
diff --git a/doc/api/next_api_changes/deprecations/29358-TH.rst b/doc/api/next_api_changes/deprecations/29358-TH.rst
new file mode 100644
index 000000000000..1b7a50456afc
--- /dev/null
+++ b/doc/api/next_api_changes/deprecations/29358-TH.rst
@@ -0,0 +1,17 @@
+3rd party scales do not need to have an *axis* parameter anymore
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Since matplotlib 3.1 `PR 12831 `_
+scale objects should be reusable and therefore independent of any particular Axis.
+Therefore, the use of the *axis* parameter in the ``__init__`` had been discouraged.
+However, having that parameter in the signature was still necessary for API
+backwards-compatibility. This is no longer the case.
+
+`.register_scale` now accepts scale classes with or without this parameter.
+
+The *axis* parameter is pending-deprecated. It will be deprecated in matplotlib 3.13,
+and removed in matplotlib 3.15.
+
+3rd-party scales are recommended to remove the *axis* parameter now if they can
+afford to restrict compatibility to matplotlib >= 3.11 already. Otherwise, they may
+keep the *axis* parameter and remove it in time for matplotlib 3.13.
diff --git a/doc/api/next_api_changes/deprecations/29993-AL.rst b/doc/api/next_api_changes/deprecations/29993-AL.rst
new file mode 100644
index 000000000000..9104fd669325
--- /dev/null
+++ b/doc/api/next_api_changes/deprecations/29993-AL.rst
@@ -0,0 +1,4 @@
+``testing.widgets.mock_event`` and ``testing.widgets.do_event``
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+... are deprecated. Directly construct Event objects (typically `.MouseEvent`
+or `.KeyEvent`) and pass them to ``canvas.callbacks.process()`` instead.
diff --git a/doc/api/next_api_changes/deprecations/30163-AL.rst b/doc/api/next_api_changes/deprecations/30163-AL.rst
new file mode 100644
index 000000000000..15d0077375f2
--- /dev/null
+++ b/doc/api/next_api_changes/deprecations/30163-AL.rst
@@ -0,0 +1,9 @@
+``matplotlib.style.core``
+~~~~~~~~~~~~~~~~~~~~~~~~~
+The ``matplotlib.style.core`` module is deprecated. All APIs intended for
+public use are now available in `matplotlib.style` directly (including
+``USER_LIBRARY_PATHS``, which was previously not reexported).
+
+The following APIs of ``matplotlib.style.core`` have been deprecated with no
+replacement: ``BASE_LIBRARY_PATH``, ``STYLE_EXTENSION``, ``STYLE_BLACKLIST``,
+``update_user_library``, ``read_style_directory``, ``update_nested_dict``.
diff --git a/doc/api/next_api_changes/deprecations/30349-AL.rst b/doc/api/next_api_changes/deprecations/30349-AL.rst
new file mode 100644
index 000000000000..78e26f41889f
--- /dev/null
+++ b/doc/api/next_api_changes/deprecations/30349-AL.rst
@@ -0,0 +1,3 @@
+``Axes.set_navigate_mode`` is deprecated
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+... with no replacement.
diff --git a/doc/api/next_api_changes/deprecations/30364-AS.rst b/doc/api/next_api_changes/deprecations/30364-AS.rst
new file mode 100644
index 000000000000..4f5493b8b706
--- /dev/null
+++ b/doc/api/next_api_changes/deprecations/30364-AS.rst
@@ -0,0 +1,4 @@
+Parameters ``Axes3D.set_aspect(..., anchor=..., share=...)``
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+The parameters *anchor* and *share* of `.Axes3D.set_aspect` are deprecated.
+They had no effect on 3D axes and will be removed in a future version.
diff --git a/doc/api/next_api_changes/deprecations/30368-AL.rst b/doc/api/next_api_changes/deprecations/30368-AL.rst
new file mode 100644
index 000000000000..efd3c8360ef3
--- /dev/null
+++ b/doc/api/next_api_changes/deprecations/30368-AL.rst
@@ -0,0 +1,3 @@
+``GridFinder.get_grid_info`` now takes a single bbox as parameter
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Passing ``x1, y1, x2, y2`` as separate parameters is deprecated.
diff --git a/doc/api/next_api_changes/deprecations/30469-AL.rst b/doc/api/next_api_changes/deprecations/30469-AL.rst
new file mode 100644
index 000000000000..ef3f042843c2
--- /dev/null
+++ b/doc/api/next_api_changes/deprecations/30469-AL.rst
@@ -0,0 +1,4 @@
+The *axes* parameter of ``RadialLocator``
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+... is deprecated. `~.polar.RadialLocator` now fetches the relevant information
+from the Axis' parent Axes.
diff --git a/doc/api/prev_api_changes/api_changes_0.90.1.rst b/doc/api/prev_api_changes/api_changes_0.90.1.rst
index 89311d4ed102..8caef5e35ced 100644
--- a/doc/api/prev_api_changes/api_changes_0.90.1.rst
+++ b/doc/api/prev_api_changes/api_changes_0.90.1.rst
@@ -32,7 +32,7 @@ Changes for 0.90.1
named units.ConversionInterface.convert.
Axes.errorbar uses Axes.vlines and Axes.hlines to draw its error
- limits int he vertical and horizontal direction. As you'll see
+ limits in the vertical and horizontal direction. As you'll see
in the changes below, these functions now return a LineCollection
rather than a list of lines. The new return signature for
errorbar is ylins, caplines, errorcollections where
diff --git a/doc/api/prev_api_changes/api_changes_1.5.0.rst b/doc/api/prev_api_changes/api_changes_1.5.0.rst
index b482d8bd7acd..513971098b93 100644
--- a/doc/api/prev_api_changes/api_changes_1.5.0.rst
+++ b/doc/api/prev_api_changes/api_changes_1.5.0.rst
@@ -189,7 +189,7 @@ algorithm that was not necessarily applicable to custom Axes. Three new private
methods, ``matplotlib.axes._base._AxesBase._get_view``,
``matplotlib.axes._base._AxesBase._set_view``, and
``matplotlib.axes._base._AxesBase._set_view_from_bbox``, allow for custom
-*Axes* classes to override the pan and zoom algorithms. Implementors of
+*Axes* classes to override the pan and zoom algorithms. Implementers of
custom *Axes* who override these methods may provide suitable behaviour for
both pan and zoom as well as the view navigation buttons on the interactive
toolbars.
diff --git a/doc/api/prev_api_changes/api_changes_3.5.0/deprecations.rst b/doc/api/prev_api_changes/api_changes_3.5.0/deprecations.rst
index 05f42035f1ac..04836687f76a 100644
--- a/doc/api/prev_api_changes/api_changes_3.5.0/deprecations.rst
+++ b/doc/api/prev_api_changes/api_changes_3.5.0/deprecations.rst
@@ -282,7 +282,7 @@ Miscellaneous deprecations
- The *format* parameter of ``dviread.find_tex_file`` is deprecated (with no
replacement).
- ``FancyArrowPatch.get_path_in_displaycoord`` and
- ``ConnectionPath.get_path_in_displaycoord`` are deprecated. The path in
+ ``ConnectionPatch.get_path_in_displaycoord`` are deprecated. The path in
display coordinates can still be obtained, as for other patches, using
``patch.get_transform().transform_path(patch.get_path())``.
- The ``font_manager.win32InstalledFonts`` and
diff --git a/doc/api/prev_api_changes/api_changes_3.7.0/removals.rst b/doc/api/prev_api_changes/api_changes_3.7.0/removals.rst
index 03239be31057..56b3ad5c253e 100644
--- a/doc/api/prev_api_changes/api_changes_3.7.0/removals.rst
+++ b/doc/api/prev_api_changes/api_changes_3.7.0/removals.rst
@@ -323,7 +323,7 @@ Miscellaneous removals
- The *format* parameter of ``dviread.find_tex_file`` is removed (with no
replacement).
- ``FancyArrowPatch.get_path_in_displaycoord`` and
- ``ConnectionPath.get_path_in_displaycoord`` are removed. The path in
+ ``ConnectionPatch.get_path_in_displaycoord`` are removed. The path in
display coordinates can still be obtained, as for other patches, using
``patch.get_transform().transform_path(patch.get_path())``.
- The ``font_manager.win32InstalledFonts`` and
diff --git a/doc/conf.py b/doc/conf.py
index 199249fdd437..4d922a5636e1 100644
--- a/doc/conf.py
+++ b/doc/conf.py
@@ -57,7 +57,7 @@ def _parse_skip_subdirs_file():
can make partial builds very fast.
"""
default_skip_subdirs = [
- 'users/prev_whats_new/*', 'users/explain/*', 'api/*', 'gallery/*',
+ 'release/prev_whats_new/*', 'users/explain/*', 'api/*', 'gallery/*',
'tutorials/*', 'plot_types/*', 'devel/*']
try:
with open(".mpl_skip_subdirs.yaml", 'r') as fin:
@@ -595,7 +595,7 @@ def js_tag_with_cache_busting(js):
# no sidebar for release notes, because that page is only a collection of links
# to sub-pages. The sidebar would repeat all the titles of the sub-pages and
# thus basically repeat all the content of the page.
- "users/release_notes": ["empty_sidebar.html"],
+ "release/release_notes": ["empty_sidebar.html"],
# '**': ['localtoc.html', 'pagesource.html']
}
diff --git a/doc/devel/MEP/MEP10.rst b/doc/devel/MEP/MEP10.rst
index 9e9650587f55..2b39959eaca7 100644
--- a/doc/devel/MEP/MEP10.rst
+++ b/doc/devel/MEP/MEP10.rst
@@ -44,8 +44,7 @@ these new features.
Numpy docstring format
----------------------
-`Numpy docstring format
-`_:
+`Numpy docstring format `_:
This format divides the docstring into clear sections, each having
different parsing rules that make the docstring easy to read both as
raw text and as HTML. We could consider alternatives, or invent our
diff --git a/doc/devel/MEP/MEP11.rst b/doc/devel/MEP/MEP11.rst
index aee44ae9a0e4..03bc3013b3e3 100644
--- a/doc/devel/MEP/MEP11.rst
+++ b/doc/devel/MEP/MEP11.rst
@@ -130,7 +130,7 @@ ordered from best/hardest to worst/easiest):
1. The distutils wininst installer allows a post-install script to
run. It might be possible to get this script to run pip_ to
install the other dependencies. (See `this thread
- `_
+ `_
for someone who has trod that ground before).
2. Continue to ship dateutil_, pytz_, six_ and pyparsing_ in
@@ -177,4 +177,4 @@ out of the box.
.. _pytz: https://pypi.org/project/pytz/
.. _setuptools: https://pypi.org/project/setuptools/
.. _six: https://pypi.org/project/six/
-.. _easy_install: https://setuptools.readthedocs.io/en/latest/easy_install.html
+.. _easy_install: https://setuptools.pypa.io/en/latest/deprecated/easy_install.html
diff --git a/doc/devel/MEP/MEP14.rst b/doc/devel/MEP/MEP14.rst
index 2c696adf8a58..d79d3c2d3115 100644
--- a/doc/devel/MEP/MEP14.rst
+++ b/doc/devel/MEP/MEP14.rst
@@ -78,7 +78,7 @@ number of other projects:
- `Microsoft DirectWrite`_
- `Apple Core Text`_
-.. _pango: https://pango.gnome.org
+.. _pango: https://github.com/GNOME/pango
.. _harfbuzz: https://github.com/harfbuzz/harfbuzz
.. _QtTextLayout: https://doc.qt.io/archives/qt-4.8/qtextlayout.html
.. _Microsoft DirectWrite: https://docs.microsoft.com/en-ca/windows/win32/directwrite/introducing-directwrite
diff --git a/doc/devel/api_changes.rst b/doc/devel/api_changes.rst
index 19bc530abf6b..5fed9f683a48 100644
--- a/doc/devel/api_changes.rst
+++ b/doc/devel/api_changes.rst
@@ -220,7 +220,7 @@ folder:
+-------------------+-----------------------------+----------------------------------------------+
| | versioning directive | announcement folder |
+===================+=============================+==============================================+
-| new feature | ``.. versionadded:: 3.N`` | :file:`doc/users/next_whats_new/` |
+| new feature | ``.. versionadded:: 3.N`` | :file:`doc/release/next_whats_new/` |
+-------------------+-----------------------------+----------------------------------------------+
| API change | ``.. versionchanged:: 3.N`` | :file:`doc/api/next_api_changes/[kind]` |
+-------------------+-----------------------------+----------------------------------------------+
@@ -306,7 +306,7 @@ API change notes
What's new notes
""""""""""""""""
-.. include:: ../users/next_whats_new/README.rst
+.. include:: ../release/next_whats_new/README.rst
:start-after: whats-new-guide-start
:end-before: whats-new-guide-end
diff --git a/doc/devel/coding_guide.rst b/doc/devel/coding_guide.rst
index 2b156cedca05..fe7769909368 100644
--- a/doc/devel/coding_guide.rst
+++ b/doc/devel/coding_guide.rst
@@ -215,7 +215,7 @@ If an end-user of Matplotlib sets up `logging` to display at levels more
verbose than ``logging.WARNING`` in their code with the Matplotlib-provided
helper::
- plt.set_loglevel("debug")
+ plt.set_loglevel("DEBUG")
or manually with ::
diff --git a/doc/devel/communication_guide.rst b/doc/devel/communication_guide.rst
index e44d9368da93..c90d1d93b99d 100644
--- a/doc/devel/communication_guide.rst
+++ b/doc/devel/communication_guide.rst
@@ -215,7 +215,7 @@ On social media, Matplotlib:
* Highlights various parts of the library, especially the more obscure bits and
bobbles.
* Acknowledges that it is a sometimes frustrating tangle of bits & bobbles that
- can confuse even the folks who work on it & signal boosts their confuzzlment.
+ can confuse even the folks who work on it & signal boosts their confuzzlement.
Behavior
diff --git a/doc/devel/release_guide.rst b/doc/devel/release_guide.rst
index 6c45bfa56c64..d1b5c963a295 100644
--- a/doc/devel/release_guide.rst
+++ b/doc/devel/release_guide.rst
@@ -83,7 +83,11 @@ Micro versions should instead read::
Check all active milestones for consistency. Older milestones should also backport
to higher meso versions (e.g. ``v3.6.3`` and ``v3.6-doc`` should backport to both
``v3.6.x`` and ``v3.7.x`` once the ``v3.7.x`` branch exists and while PR backports are
-still targeting ``v3.6.x``)
+still targeting ``v3.6.x``).
+
+Close milestones for versions that are unlikely to be released, e.g. micro versions of
+older meso releases. Remilestone issues/PRs that are now untagged to the appropriate
+future release milestone.
Create the milestone for the next-next meso release (i.e. ``v3.9.0``, as ``v3.8.0``
should already exist). While most active items should go in the next meso release,
@@ -125,22 +129,22 @@ prepare this list:
1. Archive the existing GitHub statistics page.
- a. Copy the current :file:`doc/users/github_stats.rst` to
- :file:`doc/users/prev_whats_new/github_stats_{X}.{Y}.{Z}.rst`.
+ a. Copy the current :file:`doc/release/github_stats.rst` to
+ :file:`doc/release/prev_whats_new/github_stats_{X}.{Y}.{Z}.rst`.
b. Change the link target at the top of the file.
c. Remove the "Previous GitHub Stats" section at the end.
For example, when updating from v3.7.0 to v3.7.1::
- cp doc/users/github_stats.rst doc/users/prev_whats_new/github_stats_3.7.0.rst
- $EDITOR doc/users/prev_whats_new/github_stats_3.7.0.rst
+ cp doc/release/github_stats.rst doc/release/prev_whats_new/github_stats_3.7.0.rst
+ $EDITOR doc/release/prev_whats_new/github_stats_3.7.0.rst
# Change contents as noted above.
- git add doc/users/prev_whats_new/github_stats_3.7.0.rst
+ git add doc/release/prev_whats_new/github_stats_3.7.0.rst
2. Re-generate the updated stats::
python tools/github_stats.py --since-tag v3.7.0 --milestone=v3.7.1 \
- --project 'matplotlib/matplotlib' --links > doc/users/github_stats.rst
+ --project 'matplotlib/matplotlib' --links > doc/release/github_stats.rst
3. Review and commit changes. Some issue/PR titles may not be valid reST (the most
common issue is ``*`` which is interpreted as unclosed markup). Also confirm that
@@ -194,8 +198,8 @@ What's new
*Only needed for macro and meso releases. Bugfix releases should not have new
features.*
-Merge the contents of all the files in :file:`doc/users/next_whats_new/` into a single
-file :file:`doc/users/prev_whats_new/whats_new_{X}.{Y}.0.rst` and delete the individual
+Merge the contents of all the files in :file:`doc/release/next_whats_new/` into a single
+file :file:`doc/release/prev_whats_new/whats_new_{X}.{Y}.0.rst` and delete the individual
files.
API changes
@@ -211,7 +215,7 @@ individual files.
Release notes TOC
^^^^^^^^^^^^^^^^^
-Update :file:`doc/users/release_notes.rst`:
+Update :file:`doc/release/release_notes.rst`:
- For macro and meso releases add a new section
@@ -294,9 +298,15 @@ it is important to move all branches away from the commit with the tag [#]_::
git commit --allow-empty
+Push the branch to GitHub. This is done prior to pushing the tag as a last step in ensuring
+that the branch was fully up to date. If it fails, re-fetch and recreate commits and
+tag over an up to date branch::
+
+ git push DANGER v3.7.x
+
Finally, push the tag to GitHub::
- git push DANGER v3.7.x v3.7.0
+ git push DANGER v3.7.0
Congratulations, the scariest part is done!
This assumes the release branch has already been made.
diff --git a/doc/devel/tag_guidelines.rst b/doc/devel/tag_guidelines.rst
index 2c80065982bc..2ff77d5279d5 100644
--- a/doc/devel/tag_guidelines.rst
+++ b/doc/devel/tag_guidelines.rst
@@ -61,7 +61,7 @@ Proposing new tags
1. Review existing tag list, looking out for similar entries (i.e. ``axes`` and ``axis``).
2. If a relevant tag or subcategory does not yet exist, propose it. Each tag is two
parts: ``subcategory: tag``. Tags should be one or two words.
-3. New tags should be be added when they are relevant to existing gallery entries too.
+3. New tags should be added when they are relevant to existing gallery entries too.
Avoid tags that will link to only a single gallery entry.
4. Tags can recreate other forms of organization.
diff --git a/doc/devel/testing.rst b/doc/devel/testing.rst
index 1fef85260b12..eae53c8602d4 100644
--- a/doc/devel/testing.rst
+++ b/doc/devel/testing.rst
@@ -274,14 +274,15 @@ You can also run tox on a subset of environments:
$ tox -e py310,py311
-Tox processes everything serially so it can take a long time to test
-several environments. To speed it up, you might try using a new,
-parallelized version of tox called ``detox``. Give this a try:
+Tox processes environments sequentially by default,
+which can be slow when testing multiple environments.
+To speed this up, tox now includes built-in parallelization support
+via the --parallel flag. Give it a try:
.. code-block:: bash
- $ pip install -U -i http://pypi.testrun.org detox
- $ detox
+ $ tox --parallel auto
+
Tox is configured using a file called ``tox.ini``. You may need to
edit this file if you want to add new environments to test (e.g.,
diff --git a/doc/devel/troubleshooting.rst b/doc/devel/troubleshooting.rst
index 74ce81b2da00..e57cfcb92bd6 100644
--- a/doc/devel/troubleshooting.rst
+++ b/doc/devel/troubleshooting.rst
@@ -23,7 +23,7 @@ mode::
git clean -xfd
git pull
python -m pip install -v . > build.out
- python -c "from pylab import *; set_loglevel('debug'); plot(); show()" > run.out
+ python -c "from pylab import *; set_loglevel('DEBUG'); plot(); show()" > run.out
and post :file:`build.out` and :file:`run.out` to the `matplotlib-devel
`_
diff --git a/doc/index.rst b/doc/index.rst
index 74a183d6cd7b..e77f405abb4b 100644
--- a/doc/index.rst
+++ b/doc/index.rst
@@ -14,59 +14,12 @@ and interactive visualizations.
Install
=======
-.. tab-set::
- :class: sd-width-content-min
+.. include:: install/quick_install.inc.rst
- .. tab-item:: pip
+.. toctree::
+ :hidden:
- .. code-block:: bash
-
- pip install matplotlib
-
- .. tab-item:: conda
-
- .. code-block:: bash
-
- conda install -c conda-forge matplotlib
-
- .. tab-item:: pixi
-
- .. code-block:: bash
-
- pixi add matplotlib
-
- .. tab-item:: uv
-
- .. code-block:: bash
-
- uv add matplotlib
-
- .. warning::
-
- If you install Python with ``uv`` then the ``tkagg`` backend
- will not be available because python-build-standalone (used by uv
- to distribute Python) does not contain tk bindings that are usable by
- Matplotlib (see `this issue`_ for details). If you want Matplotlib
- to be able to display plots in a window, you should install one of
- the other :ref:`supported GUI frameworks `,
- e.g.
-
- .. code-block:: bash
-
- uv add matplotlib pyside6
-
- .. _this issue: https://github.com/astral-sh/uv/issues/6893#issuecomment-2565965851
-
- .. tab-item:: other
-
- .. rst-class:: section-toc
- .. toctree::
- :maxdepth: 2
-
- install/index
-
-For more detailed instructions, see the
-:doc:`installation guide `.
+ install/index
Learn
=====
@@ -163,7 +116,7 @@ What's new
.. toctree::
:maxdepth: 1
- users/release_notes.rst
+ release/release_notes.rst
Contribute
diff --git a/doc/install/dependencies.rst b/doc/install/dependencies.rst
index 4b006d9016e2..1fb463ab18e9 100644
--- a/doc/install/dependencies.rst
+++ b/doc/install/dependencies.rst
@@ -377,7 +377,7 @@ them will be skipped by pytest.
.. _pandas: https://pypi.org/project/pandas/
.. _pikepdf: https://pypi.org/project/pikepdf/
.. _psutil: https://pypi.org/project/psutil/
-.. _pytz: https://fonts.google.com/noto/use#faq
+.. _pytz: https://pypi.org/project/pytz/
.. _pytest-cov: https://pytest-cov.readthedocs.io/en/latest/
.. _pytest-timeout: https://pypi.org/project/pytest-timeout/
.. _pytest-xdist: https://pypi.org/project/pytest-xdist/
diff --git a/doc/install/index.rst b/doc/install/index.rst
index 3e6452eb2f41..4058b0549738 100644
--- a/doc/install/index.rst
+++ b/doc/install/index.rst
@@ -1,19 +1,22 @@
.. redirect-from:: /users/installing
.. redirect-from:: /users/installing/index
+.. highlight:: sh
+
************
Installation
************
+.. include:: quick_install.inc.rst
+
+.. _install-official:
Install an official release
===========================
Matplotlib releases are available as wheel packages for macOS, Windows and
Linux on `PyPI `_. Install it using
-``pip``:
-
-.. code-block:: sh
+``pip``::
python -m pip install -U pip
python -m pip install -U matplotlib
@@ -25,16 +28,26 @@ precompiled wheel for your OS and Python.
.. note::
- The following backends work out of the box: Agg, ps, pdf, svg
+ The following non-interactive backends work out of the box: Agg,
+ ps, pdf, svg
- Python is typically shipped with tk bindings which are used by
- TkAgg. Notably, python-build-standalone – used by ``uv`` – does
- not include tk bindings that are usable by Matplotlib.
+ The TkAgg interactive backend also typically works out of the box.
+ It requires Tk bindings, which are usually provided via the Python
+ standard library's ``tkinter`` module. On some OSes, you may need
+ to install a separate package like ``python3-tk`` to add this
+ component of the standard library.
+
+ Some tools like ``uv`` make use of Python builds from the
+ python-build-standalone project, which only gained usable Tk
+ bindings recently (August 2025). If you are having trouble with the
+ TkAgg backend, ensure you have an up-to-date build, e.g. ``uv self
+ update && uv python upgrade --reinstall``.
For support of other GUI frameworks, LaTeX rendering, saving
animations and a larger selection of file formats, you can
install :ref:`optional dependencies `.
+.. _install-third-party:
Third-party distributions
=========================
@@ -44,15 +57,11 @@ Various third-parties provide Matplotlib for their environments.
Conda packages
--------------
-Matplotlib is available both via the *anaconda main channel*
-
-.. code-block:: sh
+Matplotlib is available both via the *anaconda main channel* ::
conda install matplotlib
-as well as via the *conda-forge community channel*
-
-.. code-block:: sh
+as well as via the *conda-forge community channel* ::
conda install -c conda-forge matplotlib
@@ -62,10 +71,8 @@ Python distributions
Matplotlib is part of major Python distributions:
- `Anaconda `_
-
- `ActiveState ActivePython
`_
-
- `WinPython `_
Linux package manager
@@ -81,7 +88,7 @@ you can install Matplotlib via your package manager, e.g.:
.. redirect-from:: /users/installing/installing_source
-.. _install_from_source:
+.. _install-nightly-build:
Install a nightly build
=======================
@@ -90,9 +97,7 @@ Matplotlib makes nightly development build wheels available on the
`scientific-python-nightly-wheels Anaconda Cloud organization
`_.
These wheels can be installed with ``pip`` by specifying
-scientific-python-nightly-wheels as the package index to query:
-
-.. code-block:: sh
+scientific-python-nightly-wheels as the package index to query::
python -m pip install \
--upgrade \
@@ -101,6 +106,7 @@ scientific-python-nightly-wheels as the package index to query:
--extra-index-url https://pypi.org/simple \
matplotlib
+.. _install-source:
Install from source
===================
@@ -143,8 +149,7 @@ Aspects of some behavioral defaults of the library can be configured via:
environment_variables_faq.rst
Default plotting appearance and behavior can be configured via the
-:ref:`rcParams file `
-
+:ref:`rcParams file `.
Dependencies
============
@@ -179,7 +184,7 @@ development environment such as :program:`IDLE` which add additional
complexities. Open up a UNIX shell or a DOS command prompt and run, for
example::
- python -c "from pylab import *; set_loglevel('debug'); plot(); show()"
+ python -c "from pylab import *; set_loglevel('DEBUG'); plot(); show()"
This will give you additional information about which backends Matplotlib is
loading, version information, and more. At this point you might want to make
@@ -266,13 +271,17 @@ at the Terminal.app command line::
python3 -c 'import matplotlib; print(matplotlib.__version__, matplotlib.__file__)'
-You should see something like ::
+You should see something like
+
+.. code-block:: none
3.10.0 /Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/site-packages/matplotlib/__init__.py
where ``3.10.0`` is the Matplotlib version you just installed, and the path
following depends on whether you are using Python.org Python, Homebrew or
-Macports. If you see another version, or you get an error like ::
+Macports. If you see another version, or you get an error like
+
+.. code-block:: none
Traceback (most recent call last):
File "", line 1, in
diff --git a/doc/install/quick_install.inc.rst b/doc/install/quick_install.inc.rst
new file mode 100644
index 000000000000..0604a3c8fe75
--- /dev/null
+++ b/doc/install/quick_install.inc.rst
@@ -0,0 +1,53 @@
+.. set of quick install commands for reuse across docs
+
+.. tab-set::
+ :class: sd-width-content-min
+
+ .. tab-item:: pip
+
+ .. code-block:: bash
+
+ pip install matplotlib
+
+ .. tab-item:: conda
+
+ .. code-block:: bash
+
+ conda install -c conda-forge matplotlib
+
+ .. tab-item:: pixi
+
+ .. code-block:: bash
+
+ pixi add matplotlib
+
+ .. tab-item:: uv
+
+ .. code-block:: bash
+
+ uv add matplotlib
+
+ .. warning::
+
+ uv usually installs its own versions of Python from the
+ python-build-standalone project, and only recent versions of those
+ Python builds (August 2025) work properly with the ``tkagg`` backend
+ for displaying plots in a window. Please make sure you are using uv
+ 0.8.7 or newer (update with e.g. ``uv self update``) and that your
+ bundled Python installs are up to date (with ``uv python upgrade
+ --reinstall``). Alternatively, you can use one of the other
+ :ref:`supported GUI frameworks `, e.g.
+
+ .. code-block:: bash
+
+ uv add matplotlib pyside6
+
+ .. tab-item:: other
+
+ :ref:`install-official`
+
+ :ref:`install-third-party`
+
+ :ref:`install-nightly-build`
+
+ :ref:`install-source`
diff --git a/doc/install/troubleshooting_faq.inc.rst b/doc/install/troubleshooting_faq.inc.rst
index d130813a80c6..fce94cef6a66 100644
--- a/doc/install/troubleshooting_faq.inc.rst
+++ b/doc/install/troubleshooting_faq.inc.rst
@@ -11,12 +11,11 @@ Obtaining Matplotlib version
----------------------------
To find out your Matplotlib version number, import it and print the
-``__version__`` attribute::
-
- >>> import matplotlib
- >>> matplotlib.__version__
- '0.98.0'
+``__version__`` attribute:
+>>> import matplotlib
+>>> matplotlib.__version__
+'0.98.0'
.. _locating-matplotlib-install:
@@ -24,12 +23,11 @@ To find out your Matplotlib version number, import it and print the
-----------------------------------
You can find what directory Matplotlib is installed in by importing it
-and printing the ``__file__`` attribute::
-
- >>> import matplotlib
- >>> matplotlib.__file__
- '/home/jdhunter/dev/lib64/python2.5/site-packages/matplotlib/__init__.pyc'
+and printing the ``__file__`` attribute:
+>>> import matplotlib
+>>> matplotlib.__file__
+'/home/jdhunter/dev/lib64/python2.5/site-packages/matplotlib/__init__.pyc'
.. _locating-matplotlib-config-dir:
@@ -39,32 +37,32 @@ and printing the ``__file__`` attribute::
Each user has a Matplotlib configuration directory which may contain a
:ref:`matplotlibrc ` file. To
locate your :file:`matplotlib/` configuration directory, use
-:func:`matplotlib.get_configdir`::
+:func:`matplotlib.get_configdir`:
- >>> import matplotlib as mpl
- >>> mpl.get_configdir()
- '/home/darren/.config/matplotlib'
+>>> import matplotlib as mpl
+>>> mpl.get_configdir()
+'/home/darren/.config/matplotlib'
On Unix-like systems, this directory is generally located in your
:envvar:`HOME` directory under the :file:`.config/` directory.
In addition, users have a cache directory. On Unix-like systems, this is
separate from the configuration directory by default. To locate your
-:file:`.cache/` directory, use :func:`matplotlib.get_cachedir`::
+:file:`.cache/` directory, use :func:`matplotlib.get_cachedir`:
- >>> import matplotlib as mpl
- >>> mpl.get_cachedir()
- '/home/darren/.cache/matplotlib'
+>>> import matplotlib as mpl
+>>> mpl.get_cachedir()
+'/home/darren/.cache/matplotlib'
On Windows, both the config directory and the cache directory are
the same and are in your :file:`Documents and Settings` or :file:`Users`
-directory by default::
+directory by default:
- >>> import matplotlib as mpl
- >>> mpl.get_configdir()
- 'C:\\Documents and Settings\\jdhunter\\.matplotlib'
- >>> mpl.get_cachedir()
- 'C:\\Documents and Settings\\jdhunter\\.matplotlib'
+>>> import matplotlib as mpl
+>>> mpl.get_configdir()
+'C:\\Documents and Settings\\jdhunter\\.matplotlib'
+>>> mpl.get_cachedir()
+'C:\\Documents and Settings\\jdhunter\\.matplotlib'
If you would like to use a different configuration directory, you can
do so by specifying the location in your :envvar:`MPLCONFIGDIR`
diff --git a/doc/missing-references.json b/doc/missing-references.json
index efe676afbb85..1a3693c990e5 100644
--- a/doc/missing-references.json
+++ b/doc/missing-references.json
@@ -122,8 +122,12 @@
"doc/api/_as_gen/mpl_toolkits.axisartist.floating_axes.rst:32::1"
],
"numpy.float64": [
+ "doc/docstring of matplotlib.ft2font.pybind11_detail_function_record_v1_system_libstdcpp_gxx_abi_1xxx_use_cxx11_abi_1.set_text:1",
"doc/docstring of matplotlib.ft2font.PyCapsule.set_text:1"
],
+ "numpy.typing.NDArray": [
+ "doc/docstring of matplotlib.ft2font.pybind11_detail_function_record_v1_system_libstdcpp_gxx_abi_1xxx_use_cxx11_abi_1.set_text:1"
+ ],
"numpy.uint8": [
":1"
]
diff --git a/doc/project/citing.rst b/doc/project/citing.rst
index 2cd317906bb5..ae2061e7349c 100644
--- a/doc/project/citing.rst
+++ b/doc/project/citing.rst
@@ -32,6 +32,18 @@ By version
.. START OF AUTOGENERATED
+v3.10.6
+ .. image:: ../_static/zenodo_cache/16999430.svg
+ :target: https://doi.org/10.5281/zenodo.16999430
+v3.10.5
+ .. image:: ../_static/zenodo_cache/16644850.svg
+ :target: https://doi.org/10.5281/zenodo.16644850
+v3.10.3
+ .. image:: ../_static/zenodo_cache/15375714.svg
+ :target: https://doi.org/10.5281/zenodo.15375714
+v3.10.1
+ .. image:: ../_static/zenodo_cache/14940554.svg
+ :target: https://doi.org/10.5281/zenodo.14940554
v3.10.0
.. image:: ../_static/zenodo_cache/14464227.svg
:target: https://doi.org/10.5281/zenodo.14464227
diff --git a/doc/users/generate_credits.py b/doc/project/generate_credits.py
similarity index 100%
rename from doc/users/generate_credits.py
rename to doc/project/generate_credits.py
diff --git a/doc/project/history.rst b/doc/project/history.rst
index 966b7a3caa38..7f148902898b 100644
--- a/doc/project/history.rst
+++ b/doc/project/history.rst
@@ -157,7 +157,7 @@ Matplotlib logo (2008 - 2015).
def add_math_background():
- ax = fig.add_axes([0., 0., 1., 1.])
+ ax = fig.add_axes((0., 0., 1., 1.))
text = []
text.append(
@@ -187,7 +187,7 @@ Matplotlib logo (2008 - 2015).
def add_polar_bar():
- ax = fig.add_axes([0.025, 0.075, 0.2, 0.85], projection='polar')
+ ax = fig.add_axes((0.025, 0.075, 0.2, 0.85), projection='polar')
ax.patch.set_alpha(axalpha)
ax.set_axisbelow(True)
diff --git a/doc/release/github_stats.rst b/doc/release/github_stats.rst
new file mode 100644
index 000000000000..7a06f3ce755b
--- /dev/null
+++ b/doc/release/github_stats.rst
@@ -0,0 +1,89 @@
+.. redirect-from:: /users/github_stats
+
+.. _github-stats:
+
+GitHub statistics for 3.10.6 (Aug 29, 2025)
+===========================================
+
+GitHub statistics for 2024/12/14 (tag: v3.10.0) - 2025/08/29
+
+These lists are automatically generated, and may be incomplete or contain duplicates.
+
+We closed 4 issues and merged 19 pull requests.
+The full list can be seen `on GitHub `__
+
+The following 31 authors contributed 380 commits.
+
+* Alan Burlot
+* Antony Lee
+* Christine P. Chai
+* David Stansby
+* dependabot[bot]
+* Doron Behar
+* 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 Hoffmann
+* Trygve Magnus Ræder
+
+GitHub issues and pull requests:
+
+Pull Requests (19):
+
+* :ghpull:`30487`: Backport PR #30484 on branch v3.10.x (FIX: be more cautious about checking widget size)
+* :ghpull:`30484`: FIX: be more cautious about checking widget size
+* :ghpull:`30481`: Backport PR #30394 on branch v3.10.x (ENH: Gracefully handle python-build-standalone ImportError with Tk)
+* :ghpull:`30477`: Backport PR #30476 on branch v3.10.x (ci: Remove cibuildwheel override for win_arm64/Py3.14)
+* :ghpull:`30394`: ENH: Gracefully handle python-build-standalone ImportError with Tk
+* :ghpull:`30476`: ci: Remove cibuildwheel override for win_arm64/Py3.14
+* :ghpull:`30461`: Backport PR #30451 on branch v3.10.x (doc: factor out quick install tab for reuse)
+* :ghpull:`30448`: Backport PR #30412 on branch v3.10.x ({Check,Radio}Buttons: Improve docs of label_props)
+* :ghpull:`30412`: {Check,Radio}Buttons: Improve docs of label_props
+* :ghpull:`30445`: Backport PR #30444 on branch v3.10.x (Small correction of a typo in the galleries: axis instead of axes)
+* :ghpull:`30444`: Small correction of a typo in the galleries: axis instead of axes
+* :ghpull:`30430`: Backport PR #30426 on branch v3.10.x (Fix a race condition in TexManager.make_dvi.)
+* :ghpull:`30434`: Backport PR #30426: Fix a race condition in TexManager.make_dvi & make_png.
+* :ghpull:`30431`: Use pathlib in texmanager.
+* :ghpull:`30428`: Backport PR #30399 on branch v3.10.x (Qt: Fix HiDPI handling on X11/Windows)
+* :ghpull:`30426`: Fix a race condition in TexManager.make_dvi.
+* :ghpull:`30399`: Qt: Fix HiDPI handling on X11/Windows
+* :ghpull:`30415`: Backport PR #30414 on branch v3.10.x (DOC: update Cartopy url)
+* :ghpull:`30414`: DOC: update Cartopy url
+
+Issues (4):
+
+* :ghissue:`29618`: [Bug]: FigureCanvasQT is seemingly prematurely freed under certain conditions
+* :ghissue:`30390`: [ENH]: Gracefully handle python-build-standalone ImportError
+* :ghissue:`30420`: [ENH]: Support parallel plotting
+* :ghissue:`30386`: BUG: Qt hi-dpi regression on windows and X11 with mpl 3.10.5
+
+
+Previous GitHub statistics
+--------------------------
+
+.. toctree::
+ :maxdepth: 1
+ :glob:
+ :reversed:
+
+ prev_whats_new/github_stats_*
diff --git a/doc/users/next_whats_new.rst b/doc/release/next_whats_new.rst
similarity index 80%
rename from doc/users/next_whats_new.rst
rename to doc/release/next_whats_new.rst
index ddd82faf6731..7923dde4722a 100644
--- a/doc/users/next_whats_new.rst
+++ b/doc/release/next_whats_new.rst
@@ -1,3 +1,5 @@
+.. redirect-from:: /users/next_whats_new
+
.. _whats-new:
================
diff --git a/doc/users/next_whats_new/3d_speedups.rst b/doc/release/next_whats_new/3d_speedups.rst
similarity index 100%
rename from doc/users/next_whats_new/3d_speedups.rst
rename to doc/release/next_whats_new/3d_speedups.rst
diff --git a/doc/users/next_whats_new/README.rst b/doc/release/next_whats_new/README.rst
similarity index 95%
rename from doc/users/next_whats_new/README.rst
rename to doc/release/next_whats_new/README.rst
index 23efd0208edb..a680f5120f52 100644
--- a/doc/users/next_whats_new/README.rst
+++ b/doc/release/next_whats_new/README.rst
@@ -15,7 +15,7 @@ Each new feature (e.g. function, parameter, config value, behavior, ...) must
be described through a "What's new" entry.
Each entry is written into a separate file in the
-:file:`doc/users/next_whats_new/` directory. They are sorted and merged into
+:file:`doc/release/next_whats_new/` directory. They are sorted and merged into
:file:`whats_new.rst` during the release process.
When adding an entry please look at the currently existing files to
diff --git a/doc/users/next_whats_new/axis_inversion.rst b/doc/release/next_whats_new/axis_inversion.rst
similarity index 100%
rename from doc/users/next_whats_new/axis_inversion.rst
rename to doc/release/next_whats_new/axis_inversion.rst
diff --git a/doc/users/next_whats_new/bar_label_padding_update.rst b/doc/release/next_whats_new/bar_label_padding_update.rst
similarity index 100%
rename from doc/users/next_whats_new/bar_label_padding_update.rst
rename to doc/release/next_whats_new/bar_label_padding_update.rst
diff --git a/doc/release/next_whats_new/barcontainer_properties.rst b/doc/release/next_whats_new/barcontainer_properties.rst
new file mode 100644
index 000000000000..0efe4ee00e4f
--- /dev/null
+++ b/doc/release/next_whats_new/barcontainer_properties.rst
@@ -0,0 +1,7 @@
+``BarContainer`` properties
+---------------------------
+`.BarContainer` gained new properties to easily access coordinates of the bars:
+
+- `~.BarContainer.bottoms`
+- `~.BarContainer.tops`
+- `~.BarContainer.position_centers`
diff --git a/doc/release/next_whats_new/broken_barh_align.rst b/doc/release/next_whats_new/broken_barh_align.rst
new file mode 100644
index 000000000000..5108ac5b0e9a
--- /dev/null
+++ b/doc/release/next_whats_new/broken_barh_align.rst
@@ -0,0 +1,4 @@
+``broken_barh()`` vertical alignment though ``align`` parameter
+---------------------------------------------------------------
+`~.Axes.broken_barh` now supports vertical alignment of the bars through the
+``align`` parameter.
diff --git a/doc/users/next_whats_new/color_cycle_from_sequence.rst b/doc/release/next_whats_new/color_cycle_from_sequence.rst
similarity index 100%
rename from doc/users/next_whats_new/color_cycle_from_sequence.rst
rename to doc/release/next_whats_new/color_cycle_from_sequence.rst
diff --git a/doc/users/next_whats_new/colormap_bad_under_over.rst b/doc/release/next_whats_new/colormap_bad_under_over.rst
similarity index 100%
rename from doc/users/next_whats_new/colormap_bad_under_over.rst
rename to doc/release/next_whats_new/colormap_bad_under_over.rst
diff --git a/doc/users/next_whats_new/colormap_with_alpha b/doc/release/next_whats_new/colormap_with_alpha
similarity index 100%
rename from doc/users/next_whats_new/colormap_with_alpha
rename to doc/release/next_whats_new/colormap_with_alpha
diff --git a/doc/users/next_whats_new/depthshading_improvement.rst b/doc/release/next_whats_new/depthshading_improvement.rst
similarity index 100%
rename from doc/users/next_whats_new/depthshading_improvement.rst
rename to doc/release/next_whats_new/depthshading_improvement.rst
diff --git a/doc/users/next_whats_new/figsize_unit.rst b/doc/release/next_whats_new/figsize_unit.rst
similarity index 100%
rename from doc/users/next_whats_new/figsize_unit.rst
rename to doc/release/next_whats_new/figsize_unit.rst
diff --git a/doc/users/next_whats_new/gif_savefig.rst b/doc/release/next_whats_new/gif_savefig.rst
similarity index 100%
rename from doc/users/next_whats_new/gif_savefig.rst
rename to doc/release/next_whats_new/gif_savefig.rst
diff --git a/doc/users/next_whats_new/grouped_bar.rst b/doc/release/next_whats_new/grouped_bar.rst
similarity index 100%
rename from doc/users/next_whats_new/grouped_bar.rst
rename to doc/release/next_whats_new/grouped_bar.rst
diff --git a/doc/users/next_whats_new/last_resort_font.rst b/doc/release/next_whats_new/last_resort_font.rst
similarity index 100%
rename from doc/users/next_whats_new/last_resort_font.rst
rename to doc/release/next_whats_new/last_resort_font.rst
diff --git a/doc/users/next_whats_new/log_contour_levels.rst b/doc/release/next_whats_new/log_contour_levels.rst
similarity index 100%
rename from doc/users/next_whats_new/log_contour_levels.rst
rename to doc/release/next_whats_new/log_contour_levels.rst
diff --git a/doc/users/next_whats_new/logticks.rst b/doc/release/next_whats_new/logticks.rst
similarity index 100%
rename from doc/users/next_whats_new/logticks.rst
rename to doc/release/next_whats_new/logticks.rst
diff --git a/doc/release/next_whats_new/new_rcparams_grid_options.rst b/doc/release/next_whats_new/new_rcparams_grid_options.rst
new file mode 100644
index 000000000000..c2c0455eecbb
--- /dev/null
+++ b/doc/release/next_whats_new/new_rcparams_grid_options.rst
@@ -0,0 +1,33 @@
+Separate styling options for major/minor grid line in rcParams
+--------------------------------------------------------------
+
+Using :rc:`grid.major.*` or :rc:`grid.minor.*` will overwrite the value in
+:rc:`grid.*` for the major and minor gridlines, respectively.
+
+.. plot::
+ :include-source: true
+ :alt: Modifying the gridlines using the new options `rcParams`
+
+ import matplotlib as mpl
+ import matplotlib.pyplot as plt
+
+
+ # Set visibility for major and minor gridlines
+ mpl.rcParams["axes.grid"] = True
+ mpl.rcParams["ytick.minor.visible"] = True
+ mpl.rcParams["xtick.minor.visible"] = True
+ mpl.rcParams["axes.grid.which"] = "both"
+
+ # Using old values to set both major and minor properties
+ mpl.rcParams["grid.color"] = "red"
+ mpl.rcParams["grid.linewidth"] = 1
+
+ # Overwrite some values for major and minor separately
+ mpl.rcParams["grid.major.color"] = "black"
+ mpl.rcParams["grid.major.linewidth"] = 2
+ mpl.rcParams["grid.minor.linestyle"] = ":"
+ mpl.rcParams["grid.minor.alpha"] = 0.6
+
+ plt.plot([0, 1], [0, 1])
+
+ plt.show()
diff --git a/doc/users/next_whats_new/separated_hatchcolor.rst b/doc/release/next_whats_new/separated_hatchcolor.rst
similarity index 100%
rename from doc/users/next_whats_new/separated_hatchcolor.rst
rename to doc/release/next_whats_new/separated_hatchcolor.rst
diff --git a/doc/users/next_whats_new/six_and_eight_color_petroff_color_cycles.rst b/doc/release/next_whats_new/six_and_eight_color_petroff_color_cycles.rst
similarity index 100%
rename from doc/users/next_whats_new/six_and_eight_color_petroff_color_cycles.rst
rename to doc/release/next_whats_new/six_and_eight_color_petroff_color_cycles.rst
diff --git a/doc/release/next_whats_new/sliders_callable_valfmt.rst b/doc/release/next_whats_new/sliders_callable_valfmt.rst
new file mode 100644
index 000000000000..1d350dba348a
--- /dev/null
+++ b/doc/release/next_whats_new/sliders_callable_valfmt.rst
@@ -0,0 +1,6 @@
+Callable *valfmt* for ``Slider`` and ``RangeSlider``
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+In addition to the existing %-format string, the *valfmt* parameter of
+`~.matplotlib.widgets.Slider` and `~.matplotlib.widgets.RangeSlider` now
+also accepts a callable of the form ``valfmt(val: float) -> str``.
diff --git a/doc/users/next_whats_new/streamplot_integration_control.rst b/doc/release/next_whats_new/streamplot_integration_control.rst
similarity index 100%
rename from doc/users/next_whats_new/streamplot_integration_control.rst
rename to doc/release/next_whats_new/streamplot_integration_control.rst
diff --git a/doc/users/next_whats_new/streamplot_multiple_arrows.rst b/doc/release/next_whats_new/streamplot_multiple_arrows.rst
similarity index 100%
rename from doc/users/next_whats_new/streamplot_multiple_arrows.rst
rename to doc/release/next_whats_new/streamplot_multiple_arrows.rst
diff --git a/doc/users/next_whats_new/subplots_adjust.rst b/doc/release/next_whats_new/subplots_adjust.rst
similarity index 100%
rename from doc/users/next_whats_new/subplots_adjust.rst
rename to doc/release/next_whats_new/subplots_adjust.rst
diff --git a/doc/users/next_whats_new/type1_subset.rst b/doc/release/next_whats_new/type1_subset.rst
similarity index 100%
rename from doc/users/next_whats_new/type1_subset.rst
rename to doc/release/next_whats_new/type1_subset.rst
diff --git a/doc/release/next_whats_new/updated_borderpad_parameter.rst b/doc/release/next_whats_new/updated_borderpad_parameter.rst
new file mode 100644
index 000000000000..5acf075f7b51
--- /dev/null
+++ b/doc/release/next_whats_new/updated_borderpad_parameter.rst
@@ -0,0 +1,18 @@
+``borderpad`` accepts a tuple for separate x/y padding
+-------------------------------------------------------
+
+The ``borderpad`` parameter used for placing anchored artists (such as inset axes) now accepts a tuple of ``(x_pad, y_pad)``.
+
+This allows for specifying separate padding values for the horizontal and
+vertical directions, providing finer control over placement. For example, when
+placing an inset in a corner, one might want horizontal padding to avoid
+overlapping with the main plot's axis labels, but no vertical padding to keep
+the inset flush with the plot area edge.
+
+Example usage with :func:`~mpl_toolkits.axes_grid1.inset_locator.inset_axes`:
+
+.. code-block:: python
+
+ ax_inset = inset_axes(
+ ax, width="30%", height="30%", loc='upper left',
+ borderpad=(4, 0))
diff --git a/doc/users/next_whats_new/violinplot_colors.rst b/doc/release/next_whats_new/violinplot_colors.rst
similarity index 100%
rename from doc/users/next_whats_new/violinplot_colors.rst
rename to doc/release/next_whats_new/violinplot_colors.rst
diff --git a/doc/release/next_whats_new/webagg_capture_scroll.rst b/doc/release/next_whats_new/webagg_capture_scroll.rst
new file mode 100644
index 000000000000..106e1ebdf36b
--- /dev/null
+++ b/doc/release/next_whats_new/webagg_capture_scroll.rst
@@ -0,0 +1,7 @@
+WebAgg scroll capture control
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The WebAgg backend now provides the ability to capture scroll events to prevent
+page scrolling when interacting with plots. This can be enabled or disabled via
+the new `.FigureCanvasWebAggCore.set_capture_scroll` and
+`.FigureCanvasWebAggCore.get_capture_scroll` methods.
diff --git a/doc/users/next_whats_new/xtick_ytick_rotation_modes.rst b/doc/release/next_whats_new/xtick_ytick_rotation_modes.rst
similarity index 100%
rename from doc/users/next_whats_new/xtick_ytick_rotation_modes.rst
rename to doc/release/next_whats_new/xtick_ytick_rotation_modes.rst
diff --git a/doc/users/prev_whats_new/changelog.rst b/doc/release/prev_whats_new/changelog.rst
similarity index 99%
rename from doc/users/prev_whats_new/changelog.rst
rename to doc/release/prev_whats_new/changelog.rst
index 8f505e4fdd37..47b1fb68b09a 100644
--- a/doc/users/prev_whats_new/changelog.rst
+++ b/doc/release/prev_whats_new/changelog.rst
@@ -1,10 +1,12 @@
+.. redirect-from:: /users/prev_whats_new/changelog
+
.. _old_changelog:
List of changes to Matplotlib prior to 2015
===========================================
This is a list of the changes made to Matplotlib from 2003 to 2015. For more
-recent changes, please refer to the :doc:`/users/release_notes`.
+recent changes, please refer to the :doc:`/release/release_notes`.
2015-11-16
Levels passed to contour(f) and tricontour(f) must be in increasing order.
@@ -1689,7 +1691,7 @@ recent changes, please refer to the :doc:`/users/release_notes`.
required by the experimental traited config and are somewhat out of date.
If needed, install them independently, see
http://code.enthought.com/pages/traits.html and
- http://www.voidspace.org.uk/python/configobj.html
+ https://configobj.readthedocs.io/en/latest/
2008-12-12
Added support to assign labels to histograms of multiple data. - MM
@@ -4272,7 +4274,7 @@ recent changes, please refer to the :doc:`/users/release_notes`.
2006-01-11
Released 0.86.1
-2006-01-11
+2006-01-11
Fixed setup.py for win32 build and added rc template to the MANIFEST.in
2006-01-10
diff --git a/doc/users/prev_whats_new/dflt_style_changes.rst b/doc/release/prev_whats_new/dflt_style_changes.rst
similarity index 99%
rename from doc/users/prev_whats_new/dflt_style_changes.rst
rename to doc/release/prev_whats_new/dflt_style_changes.rst
index a833064b573b..e4697cf2c451 100644
--- a/doc/users/prev_whats_new/dflt_style_changes.rst
+++ b/doc/release/prev_whats_new/dflt_style_changes.rst
@@ -1,3 +1,4 @@
+.. redirect-from:: /users/prev_whats_new/dflt_style_changes
.. redirect-from:: /users/dflt_style_changes
==============================
@@ -1005,7 +1006,7 @@ a cleaner separation between subplots.
ax = fig.add_subplot(2, 2, j)
ax.hist(np.random.beta(0.5, 0.5, 10000), 25, density=True)
- ax.set_xlim([0, 1])
+ ax.set_xlim(0, 1)
ax.set_title(title)
ax = fig.add_subplot(2, 2, j + 2)
diff --git a/doc/users/prev_whats_new/github_stats_3.0.0.rst b/doc/release/prev_whats_new/github_stats_3.0.0.rst
similarity index 99%
rename from doc/users/prev_whats_new/github_stats_3.0.0.rst
rename to doc/release/prev_whats_new/github_stats_3.0.0.rst
index 0e9c4b3b588d..dd17bc0fece7 100644
--- a/doc/users/prev_whats_new/github_stats_3.0.0.rst
+++ b/doc/release/prev_whats_new/github_stats_3.0.0.rst
@@ -1,3 +1,5 @@
+.. redirect-from:: /users/prev_whats_new/github_stats_3.0.0
+
.. _github-stats-3-0-0:
GitHub statistics for 3.0.0 (Sep 18, 2018)
@@ -595,7 +597,7 @@ Pull Requests (598):
* :ghpull:`11757`: PGF backend output text color even if black
* :ghpull:`11751`: Remove the unused 'verbose' option from setupext.
* :ghpull:`9084`: Require calling a _BoundMethodProxy to get the underlying callable.
-* :ghpull:`11752`: Fix section level of Previous Whats New
+* :ghpull:`11752`: Fix section level of Previous What's New
* :ghpull:`10513`: Replace most uses of getfilesystemencoding by os.fs{en,de}code.
* :ghpull:`11739`: fix tight_layout bug #11737
* :ghpull:`11744`: minor doc update on axes_grid1's inset_axes
@@ -899,7 +901,7 @@ Pull Requests (598):
* :ghpull:`11075`: Drop alpha channel when saving comparison failure diff image.
* :ghpull:`9022`: Help tool
* :ghpull:`11045`: Help tool.
-* :ghpull:`11076`: Don't create texput.{aux,log} in rootdir everytime tests are run.
+* :ghpull:`11076`: Don't create texput.{aux,log} in rootdir every time tests are run
* :ghpull:`11073`: py3fication of some tests.
* :ghpull:`11074`: bytes % args is back since py3.5
* :ghpull:`11066`: Use chained comparisons where reasonable.
@@ -1138,7 +1140,7 @@ Issues (123):
* :ghissue:`11373`: Passing an incorrectly sized colour list to scatter should raise a relevant error
* :ghissue:`11756`: pgf backend doesn't set color of text when the color is black
* :ghissue:`11766`: test_axes.py::test_csd_freqs failing with numpy 1.15.0 on macOS
-* :ghissue:`11750`: previous whats new is overindented on "what's new in mpl3.0 page"
+* :ghissue:`11750`: previous what's new is overindented on "what's new in mpl3.0 page"
* :ghissue:`11728`: Qt5 Segfaults on window resize
* :ghissue:`11709`: Repaint region is wrong on Retina display with Qt5
* :ghissue:`11578`: wx segfaulting on OSX travis tests
@@ -1149,7 +1151,7 @@ Issues (123):
* :ghissue:`11607`: AttributeError: 'QEvent' object has no attribute 'pos'
* :ghissue:`11486`: Colorbar does not render with PowerNorm and min extend when using imshow
* :ghissue:`11582`: wx segfault
-* :ghissue:`11515`: using 'sharex' once in 'subplots' function can affect subsequent calles to 'subplots'
+* :ghissue:`11515`: using 'sharex' once in 'subplots' function can affect subsequent calls to 'subplots'
* :ghissue:`10269`: input() blocks any rendering and event handling
* :ghissue:`10345`: Python 3.4 with Matplotlib 1.5 vs Python 3.6 with Matplotlib 2.1
* :ghissue:`10443`: Drop use of pytz dependency in next major release
diff --git a/doc/users/prev_whats_new/github_stats_3.0.1.rst b/doc/release/prev_whats_new/github_stats_3.0.1.rst
similarity index 99%
rename from doc/users/prev_whats_new/github_stats_3.0.1.rst
rename to doc/release/prev_whats_new/github_stats_3.0.1.rst
index 95e899d1a9de..eaa0f88ba22a 100644
--- a/doc/users/prev_whats_new/github_stats_3.0.1.rst
+++ b/doc/release/prev_whats_new/github_stats_3.0.1.rst
@@ -1,3 +1,5 @@
+.. redirect-from:: /users/prev_whats_new/github_stats_3.0.1
+
.. _github-stats-3-0-1:
GitHub statistics for 3.0.1 (Oct 25, 2018)
@@ -150,7 +152,7 @@ Pull Requests (127):
* :ghpull:`12230`: Backport PR #12213 on branch v3.0.x (Change win32InstalledFonts return value)
* :ghpull:`12213`: Change win32InstalledFonts return value
* :ghpull:`12223`: Backport PR #11688 on branch v3.0.x (Don't draw axis (spines, ticks, labels) twice when using parasite axes.)
-* :ghpull:`12224`: Backport PR #12207 on branch v3.0.x (FIX: dont' check for interactive framework if none required)
+* :ghpull:`12224`: Backport PR #12207 on branch v3.0.x (FIX: don't check for interactive framework if none required)
* :ghpull:`12207`: FIX: don't check for interactive framework if none required
* :ghpull:`11688`: Don't draw axis (spines, ticks, labels) twice when using parasite axes.
* :ghpull:`12205`: Backport PR #12186 on branch v3.0.x (DOC: fix API note about get_tightbbox)
diff --git a/doc/users/prev_whats_new/github_stats_3.0.2.rst b/doc/release/prev_whats_new/github_stats_3.0.2.rst
similarity index 99%
rename from doc/users/prev_whats_new/github_stats_3.0.2.rst
rename to doc/release/prev_whats_new/github_stats_3.0.2.rst
index c5caed404b62..45c99e990147 100644
--- a/doc/users/prev_whats_new/github_stats_3.0.2.rst
+++ b/doc/release/prev_whats_new/github_stats_3.0.2.rst
@@ -1,3 +1,5 @@
+.. redirect-from:: /users/prev_whats_new/github_stats_3.0.2
+
.. _github-stats-3-0-2:
GitHub statistics for 3.0.2 (Nov 10, 2018)
@@ -83,7 +85,7 @@ Pull Requests (224):
* :ghpull:`12670`: FIX: add setter for hold to un-break basemap
* :ghpull:`12693`: Workaround Text3D breaking tight_layout()
* :ghpull:`12727`: Reorder API docs: separate file per module
-* :ghpull:`12738`: Add unobtrusive depreaction note to the first line of the docstring.
+* :ghpull:`12738`: Add unobtrusive deprecation note to the first line of the docstring
* :ghpull:`12740`: DOC: constrained layout guide (fix: Spacing with colorbars)
* :ghpull:`11663`: Refactor color parsing of Axes.scatter
* :ghpull:`12736`: Move deprecation note to end of docstring
@@ -263,7 +265,7 @@ Pull Requests (224):
* :ghpull:`12227`: Use (float, float) as parameter type for 2D positions
* :ghpull:`12199`: Allow disabling specific mouse actions in blocking_input
* :ghpull:`12213`: Change win32InstalledFonts return value
-* :ghpull:`12207`: FIX: dont' check for interactive framework if none required
+* :ghpull:`12207`: FIX: don't check for interactive framework if none required
* :ghpull:`11688`: Don't draw axis (spines, ticks, labels) twice when using parasite axes.
* :ghpull:`12210`: Axes.tick_params() argument checking
* :ghpull:`12211`: Fix typo
diff --git a/doc/users/prev_whats_new/github_stats_3.0.3.rst b/doc/release/prev_whats_new/github_stats_3.0.3.rst
similarity index 99%
rename from doc/users/prev_whats_new/github_stats_3.0.3.rst
rename to doc/release/prev_whats_new/github_stats_3.0.3.rst
index 5c1271e52e4f..a70c83ecfec8 100644
--- a/doc/users/prev_whats_new/github_stats_3.0.3.rst
+++ b/doc/release/prev_whats_new/github_stats_3.0.3.rst
@@ -1,3 +1,5 @@
+.. redirect-from:: /users/prev_whats_new/github_stats_3.0.3
+
.. _github-stats-3-0-3:
GitHub statistics for 3.0.3 (Feb 28, 2019)
diff --git a/doc/users/prev_whats_new/github_stats_3.1.0.rst b/doc/release/prev_whats_new/github_stats_3.1.0.rst
similarity index 99%
rename from doc/users/prev_whats_new/github_stats_3.1.0.rst
rename to doc/release/prev_whats_new/github_stats_3.1.0.rst
index 97bee1af56b8..fe553a4af8f3 100644
--- a/doc/users/prev_whats_new/github_stats_3.1.0.rst
+++ b/doc/release/prev_whats_new/github_stats_3.1.0.rst
@@ -1,3 +1,5 @@
+.. redirect-from:: /users/prev_whats_new/github_stats_3.1.0
+
.. _github-stats-3-1-0:
GitHub statistics for 3.1.0 (May 18, 2019)
@@ -871,7 +873,7 @@ Pull Requests (918):
* :ghpull:`12749`: Move toolmanager warning from logging to warning.
* :ghpull:`12598`: Support Cn colors with n>=10.
* :ghpull:`12727`: Reorder API docs: separate file per module
-* :ghpull:`12738`: Add unobtrusive depreaction note to the first line of the docstring.
+* :ghpull:`12738`: Add unobtrusive deprecation note to the first line of the docstring
* :ghpull:`11663`: Refactor color parsing of Axes.scatter
* :ghpull:`12736`: Move deprecation note to end of docstring
* :ghpull:`12704`: Rename tkinter import from Tk to tk.
diff --git a/doc/users/prev_whats_new/github_stats_3.1.1.rst b/doc/release/prev_whats_new/github_stats_3.1.1.rst
similarity index 99%
rename from doc/users/prev_whats_new/github_stats_3.1.1.rst
rename to doc/release/prev_whats_new/github_stats_3.1.1.rst
index 3e552c371c55..a84fe93d6808 100644
--- a/doc/users/prev_whats_new/github_stats_3.1.1.rst
+++ b/doc/release/prev_whats_new/github_stats_3.1.1.rst
@@ -1,3 +1,5 @@
+.. redirect-from:: /users/prev_whats_new/github_stats_3.1.1
+
.. _github-stats-3-1-1:
GitHub statistics for 3.1.1 (Jul 02, 2019)
diff --git a/doc/users/prev_whats_new/github_stats_3.1.2.rst b/doc/release/prev_whats_new/github_stats_3.1.2.rst
similarity index 98%
rename from doc/users/prev_whats_new/github_stats_3.1.2.rst
rename to doc/release/prev_whats_new/github_stats_3.1.2.rst
index e1ed84e26372..c966c2fbcaf0 100644
--- a/doc/users/prev_whats_new/github_stats_3.1.2.rst
+++ b/doc/release/prev_whats_new/github_stats_3.1.2.rst
@@ -1,3 +1,5 @@
+.. redirect-from:: /users/prev_whats_new/github_stats_3.1.2
+
.. _github-stats-3-1-2:
GitHub statistics for 3.1.2 (Nov 21, 2019)
@@ -172,7 +174,7 @@ Issues (28):
* :ghissue:`15162`: axes.bar fails when x is int-indexed pandas.Series
* :ghissue:`15103`: Colorbar for imshow messes interactive cursor with masked data
* :ghissue:`8744`: ConnectionPatch hidden by plots
-* :ghissue:`14950`: plt.ioff() not supressing figure generation
+* :ghissue:`14950`: plt.ioff() not suppressing figure generation
* :ghissue:`14959`: Typo in Docs
* :ghissue:`14902`: from matplotlib import animation UnicodeDecodeError
* :ghissue:`14897`: New yticks behavior in 3.1.1 vs 3.1.0
diff --git a/doc/users/prev_whats_new/github_stats_3.1.3.rst b/doc/release/prev_whats_new/github_stats_3.1.3.rst
similarity index 96%
rename from doc/users/prev_whats_new/github_stats_3.1.3.rst
rename to doc/release/prev_whats_new/github_stats_3.1.3.rst
index b4706569df02..604606e98a42 100644
--- a/doc/users/prev_whats_new/github_stats_3.1.3.rst
+++ b/doc/release/prev_whats_new/github_stats_3.1.3.rst
@@ -1,3 +1,5 @@
+.. redirect-from:: /users/prev_whats_new/github_stats_3.1.3
+
.. _github-stats-3-1-3:
GitHub statistics for 3.1.3 (Feb 03, 2020)
@@ -71,8 +73,8 @@ Pull Requests (45):
* :ghpull:`15757`: Backport PR #15751 on branch v3.1.x (Modernize FAQ entry for plt.show().)
* :ghpull:`15735`: Cleanup some mplot3d docstrings.
* :ghpull:`15753`: Backport PR #15661 on branch v3.1.x (Document scope of 3D scatter depthshading.)
-* :ghpull:`15741`: Backport PR #15729 on branch v3.1.x (Catch correct parse errror type for dateutil >= 2.8.1)
-* :ghpull:`15729`: Catch correct parse errror type for dateutil >= 2.8.1
+* :ghpull:`15741`: Backport PR #15729 on branch v3.1.x (Catch correct parse error type for dateutil >= 2.8.1)
+* :ghpull:`15729`: Catch correct parse error type for dateutil >= 2.8.1
* :ghpull:`15737`: Fix env override in WebAgg backend test.
* :ghpull:`15244`: Change documentation format of rcParams defaults
diff --git a/doc/users/prev_whats_new/github_stats_3.10.0.rst b/doc/release/prev_whats_new/github_stats_3.10.0.rst
similarity index 99%
rename from doc/users/prev_whats_new/github_stats_3.10.0.rst
rename to doc/release/prev_whats_new/github_stats_3.10.0.rst
index 01b54708b7ec..d61150e6bd6a 100644
--- a/doc/users/prev_whats_new/github_stats_3.10.0.rst
+++ b/doc/release/prev_whats_new/github_stats_3.10.0.rst
@@ -1,3 +1,5 @@
+.. redirect-from:: /users/prev_whats_new/github_stats_3.10.0
+
.. _github-stats-3_10_0:
GitHub statistics for 3.10.0 (Dec 13, 2024)
diff --git a/doc/users/github_stats.rst b/doc/release/prev_whats_new/github_stats_3.10.1.rst
similarity index 98%
rename from doc/users/github_stats.rst
rename to doc/release/prev_whats_new/github_stats_3.10.1.rst
index de1f85004f09..c7fac6d5c61d 100644
--- a/doc/users/github_stats.rst
+++ b/doc/release/prev_whats_new/github_stats_3.10.1.rst
@@ -1,4 +1,4 @@
-.. _github-stats:
+.. _github-stats-3_10_1:
GitHub statistics for 3.10.1 (Feb 27, 2025)
===========================================
@@ -169,14 +169,3 @@ Issues (14):
* :ghissue:`25274`: [Bug]: .remove() on ErrorbarContainer object does not remove the corresponding item from the legend
* :ghissue:`29202`: [Bug]: ``fontsize`` in tables not working
* :ghissue:`29301`: [Bug]: Blank EPS output with legend and annotate
-
-
-Previous GitHub statistics
---------------------------
-
-.. toctree::
- :maxdepth: 1
- :glob:
- :reversed:
-
- prev_whats_new/github_stats_*
diff --git a/doc/release/prev_whats_new/github_stats_3.10.3.rst b/doc/release/prev_whats_new/github_stats_3.10.3.rst
new file mode 100644
index 000000000000..3e1d350dae5c
--- /dev/null
+++ b/doc/release/prev_whats_new/github_stats_3.10.3.rst
@@ -0,0 +1,144 @@
+.. _github-stats_3-10-3:
+
+GitHub statistics for 3.10.3 (May 08, 2025)
+===========================================
+
+GitHub statistics for 2025/02/27 (tag: v3.10.1) - 2025/05/08
+
+These lists are automatically generated, and may be incomplete or contain duplicates.
+
+We closed 16 issues and merged 78 pull requests.
+The full list can be seen `on GitHub `__
+
+The following 28 authors contributed 128 commits.
+
+* Alexandra Khoo
+* Antony Lee
+* Carlos Ramos Carreño
+* David Lowry-Duda
+* David Stansby
+* DerWeh
+* Elliott Sales de Andrade
+* guillermodotn
+* hannah
+* Hassan Kibirige
+* Ian Thomas
+* James Addison
+* Jody Klymak
+* Kyle Sunden
+* Marten H. van Kerkwijk
+* Marten Henric van Kerkwijk
+* martincornejo
+* Mateusz Sokół
+* Nicolai Weitkemper
+* Oscar Gustafsson
+* Praful Gulani
+* prafulgulani555
+* Qian Zhang
+* Raphael Erik Hviding
+* Ruth Comer
+* Thomas A Caswell
+* Tim Hoffmann
+* Weh Andreas
+
+GitHub issues and pull requests:
+
+Pull Requests (78):
+
+* :ghpull:`30018`: Backport PR #29907 on branch v3.10.x (Ensure text metric calculation always uses the text cache)
+* :ghpull:`30010`: Backport PR #29992 on v3.10.x: Update pinned oldest win image on azure
+* :ghpull:`29992`: Update pinned oldest win image on azure
+* :ghpull:`29867`: Backport PR #29827 on branch v3.10.x (TST: Remove unnecessary test images)
+* :ghpull:`30002`: Backport PR #29673 on branch v3.10.x (DOC: document the issues with overlaying new mpl on old mpl)
+* :ghpull:`29673`: DOC: document the issues with overlaying new mpl on old mpl
+* :ghpull:`29999`: Backport PR #29997 on branch v3.10.x (BLD: Ensure meson.build has the right version of Python)
+* :ghpull:`29997`: BLD: Ensure meson.build has the right version of Python
+* :ghpull:`29996`: Backport PR #29995 on branch v3.10.x (Fix typo: missing singlequote in unrecognized backend exception)
+* :ghpull:`29995`: Fix typo: missing singlequote in unrecognized backend exception
+* :ghpull:`29990`: Backport PR #29789 on branch v3.10.x (Improve layout of cheatsheets in sidebar)
+* :ghpull:`29987`: Backport PR #29370 on branch v3.10.x (DOC: Improve NonUniformImage docs)
+* :ghpull:`29370`: DOC: Improve NonUniformImage docs
+* :ghpull:`29983`: Backport PR #29975 on branch v3.10.x (DOC: correct signature for animation update function in rain example)
+* :ghpull:`29974`: Backport PR #29970 on branch v3.10.x (TST: Make refcount tests more resilient to Python changes)
+* :ghpull:`29975`: DOC: correct signature for animation update function in rain example
+* :ghpull:`29980`: Backport PR #29979 on branch v3.10.x (Fix typos: horizonatal -> horizontal)
+* :ghpull:`29979`: Fix typos: horizonatal -> horizontal
+* :ghpull:`29970`: TST: Make refcount tests more resilient to Python changes
+* :ghpull:`29969`: Backport PR #29965 on branch v3.10.x (Document Axes.spines)
+* :ghpull:`29965`: Document Axes.spines
+* :ghpull:`29949`: Backport PR #29796 on branch v3.10.x: ci: rotate soon-to-be-unsupported GitHub Actions ubuntu-20.04 runner out of roster
+* :ghpull:`29901`: Backport PR #29872 on branch v3.10.x (TST: Use placeholders for text in layout tests)
+* :ghpull:`29933`: Backport PR #29931 on branch v3.10.x (Allow Python native sequences in Matplotlib ``imsave()``.)
+* :ghpull:`29943`: Fix doc build on 3.10.x
+* :ghpull:`29940`: Backport PR #29919 on branch v3.10.x (Handle MOVETO's, CLOSEPOLY's and empty paths in Path.interpolated)
+* :ghpull:`29919`: Handle MOVETO's, CLOSEPOLY's and empty paths in Path.interpolated
+* :ghpull:`29908`: TST: Use text placeholders for empty legends
+* :ghpull:`29931`: Allow Python native sequences in Matplotlib ``imsave()``.
+* :ghpull:`29932`: Backport PR #29920 on branch v3.10.x (Allow ``None`` in set_prop_cycle (in type hints))
+* :ghpull:`29920`: Allow ``None`` in set_prop_cycle (in type hints)
+* :ghpull:`29927`: Backport PR #29897 on branch v3.10.x (BUG: ensure that errorbar does not error on masked negative errors.)
+* :ghpull:`29930`: Backport PR #29929 on branch v3.10.x (Correct rightparen typo)
+* :ghpull:`29929`: Correct rightparen typo
+* :ghpull:`29897`: BUG: ensure that errorbar does not error on masked negative errors.
+* :ghpull:`29907`: Ensure text metric calculation always uses the text cache
+* :ghpull:`29902`: Backport PR #29899 on branch v3.10.x ([doc] minimally document what basic units is doing)
+* :ghpull:`29900`: Backport PR #29896 on branch v3.10.x (Change ``.T`` to ``.transpose()`` in ``_reshape_2D``)
+* :ghpull:`29872`: TST: Use placeholders for text in layout tests
+* :ghpull:`29896`: Change ``.T`` to ``.transpose()`` in ``_reshape_2D``
+* :ghpull:`29888`: Backport PR #29803 on branch v3.10.x (DOC: Improve FancyArrowPatch docstring)
+* :ghpull:`29880`: Backport PR #29853 on branch v3.10.x (Update lib/matplotlib/stackplot.py)
+* :ghpull:`29853`: Update lib/matplotlib/stackplot.py
+* :ghpull:`29868`: Backport PR #29834 on branch v3.10.x (TST: pin flake8)
+* :ghpull:`29827`: TST: Remove unnecessary test images
+* :ghpull:`29861`: Backport PR #29773 on branch v3.10.x (DOC: Improve interactive figures guide / Blocking input)
+* :ghpull:`29859`: Backport PR #29545 on branch v3.10.x (DOC: correctly specify return type of ``figaspect``)
+* :ghpull:`29545`: DOC: correctly specify return type of ``figaspect``
+* :ghpull:`29858`: Backport PR #29842 on branch v3.10.x (Don't drag draggables on scroll events)
+* :ghpull:`29842`: Don't drag draggables on scroll events
+* :ghpull:`29848`: Backport PR #29839 on branch v3.10.x (Improve docs regarding plt.close().)
+* :ghpull:`29839`: Improve docs regarding plt.close().
+* :ghpull:`29818`: Backport PR #29801 on branch v3.10.x (DOC: Slightly further improve arrowstyle demo)
+* :ghpull:`29814`: Backport PR #29552 on branch v3.10.x (Bug Fix: Normalize kwargs for Histogram)
+* :ghpull:`29792`: Backport PR #29770 on branch v3.10.x (MNT: Move test for old ipython behavior to minver tests)
+* :ghpull:`29750`: Backport PR #29748 on branch v3.10.x (Fix PyGObject version pinning in macOS tests)
+* :ghpull:`29754`: Backport PR #29721 on branch v3.10.x (FIX: pyplot auto-backend detection case-sensitivity fixup)
+* :ghpull:`29786`: Backport PR #29755 on branch v3.10.x (DOC: Simplify annotation arrow style reference)
+* :ghpull:`29784`: Backport PR #29781 on branch v3.10.x (Fix escaping of nulls and "0" in default filenames.)
+* :ghpull:`29781`: Fix escaping of nulls and "0" in default filenames.
+* :ghpull:`29771`: Backport PR #29752 on branch v3.10.x (DOC: Add install instructions for pixi and uv)
+* :ghpull:`29768`: Backport PR #29767 on branch v3.10.x (Add description to logit_demo.py script)
+* :ghpull:`29721`: FIX: pyplot auto-backend detection case-sensitivity fixup
+* :ghpull:`29737`: Backport PR #29734 on branch v3.10.x (ci: MacOS 14: temporarily upper-bound the 'PyGObject' Python package version)
+* :ghpull:`29735`: Backport PR #29719 on branch v3.10.x (Fix passing singleton sequence-type styles to hist)
+* :ghpull:`29719`: Fix passing singleton sequence-type styles to hist
+* :ghpull:`29730`: Backport PR #29724 on branch v3.10.x (Fix SubplotSpec.get_gridspec type hint)
+* :ghpull:`29724`: Fix SubplotSpec.get_gridspec type hint
+* :ghpull:`29727`: Backport PR #29726 on branch v3.10.x (Add reference tag to Hatch style reference)
+* :ghpull:`29709`: Backport PR #29708 on branch v3.10.x (MNT: correct version in plotting method deprecation warnings)
+* :ghpull:`29708`: MNT: correct version in plotting method deprecation warnings
+* :ghpull:`29692`: Backport PR #29689 on branch v3.10.x (Fix alt and caption handling in Sphinx directives)
+* :ghpull:`29693`: Backport PR #29590 on branch v3.10.x (Blocked set_clim() callbacks to prevent inconsistent state (#29522))
+* :ghpull:`29590`: Blocked set_clim() callbacks to prevent inconsistent state (#29522)
+* :ghpull:`29689`: Fix alt and caption handling in Sphinx directives
+* :ghpull:`29691`: Backport PR #29584 on branch v3.10.x (DOC: Recommend constrained_layout over tight_layout)
+* :ghpull:`29584`: DOC: Recommend constrained_layout over tight_layout
+* :ghpull:`29552`: Bug Fix: Normalize kwargs for Histogram
+
+Issues (16):
+
+* :ghissue:`29183`: [Bug]: I give an RGB image to imsave but I don't have the right color map!
+* :ghissue:`29797`: [MNT]: Flaky Windows_py31x tests on Azure Pipelines
+* :ghissue:`26827`: [Bug]: ImportError when using Matplotlib v3.8.0 in Python package tests
+* :ghissue:`29964`: [Doc]: object description for "spines"of matplot.axes.Axes not found
+* :ghissue:`29917`: [Bug]: Contour plots using mollweide-projection
+* :ghissue:`29540`: [Bug]: matshow(..., fignum=...) broken
+* :ghissue:`29142`: [MNT]: Draggable legend gets stuck on cursor after scroll event
+* :ghissue:`29857`: [Bug]: Unexpected behavior of the line style specifiers in the histogram method
+* :ghissue:`29766`: [MNT]: ci: ubuntu-20.04 GitHub Actions runner will soon be unmaintained
+* :ghissue:`29812`: [MNT]: Backport request for #29552 to 3.10.x
+* :ghissue:`29779`: [Bug]: get_default_filename removes '0' from file name instead of '\0' from window title
+* :ghissue:`29713`: [Bug]: Matplotlib selects TkAgg backend on LXC containers
+* :ghissue:`29717`: [Bug]: Using a linestyle tuple with a histogram crashes with matplotlib 3.10
+* :ghissue:`29522`: [Bug]: Image color limits not correctly updated with set_clim() IFF color bar present AND new norm.vmin > old norm.vmax
+* :ghissue:`17339`: Clarify that constrained_layout and tight_layout conflict with each other
+* :ghissue:`28884`: [ENH]: Expand ``hist()`` signature to support aliases and plural kwargs
diff --git a/doc/release/prev_whats_new/github_stats_3.10.5.rst b/doc/release/prev_whats_new/github_stats_3.10.5.rst
new file mode 100644
index 000000000000..319086baebe5
--- /dev/null
+++ b/doc/release/prev_whats_new/github_stats_3.10.5.rst
@@ -0,0 +1,142 @@
+.. _github-stats-3_10_5:
+
+GitHub statistics for 3.10.5 (Jul 31, 2025)
+===========================================
+
+GitHub statistics for 2024/12/14 (tag: v3.10.0) - 2025/07/31
+
+These lists are automatically generated, and may be incomplete or contain duplicates.
+
+We closed 18 issues and merged 67 pull requests.
+The full list can be seen `on GitHub `__
+
+The following 36 authors contributed 371 commits.
+
+* Antony Lee
+* Brian Christian
+* chrisjbillington
+* Christine P. Chai
+* Clément Robert
+* David Stansby
+* dependabot[bot]
+* Elliott Sales de Andrade
+* G.D. McBain
+* Greg Lucas
+* hannah
+* hu-xiaonan
+* Ian Thomas
+* ianlv
+* IdiotCoffee
+* Ines Cachola
+* 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
+* Roman A
+* Ruth Comer
+* saikarna913
+* Scott Shambaugh
+* Thomas A Caswell
+* Tim Hoffmann
+* Trygve Magnus Ræder
+
+GitHub issues and pull requests:
+
+Pull Requests (67):
+
+* :ghpull:`30357`: CIBW updates: fix pypy sections, update cibw version
+* :ghpull:`30356`: Manual Backport PR #30195 on branch v3.10.x (ci: Enable wheel builds on Python 3.14)
+* :ghpull:`30352`: Backport PR #28554 on branch v3.10.x (BLD: Enable wheels on Windows-on-ARM)
+* :ghpull:`30353`: Backport PR #30345 on branch v3.10.x (qt: Use better devicePixelRatio event to refresh scaling)
+* :ghpull:`30350`: Backport PR #30344 on branch v3.10.x (Support fractional HiDPI in GTK4 backend)
+* :ghpull:`30277`: Backport PR #30271 on branch v3.10.x (Reduce pause time in interactive timer test)
+* :ghpull:`30351`: Backport PR #30327 on branch v3.10.x (FIX Update Axes limits from Axes.add_collection(... autolim=True))
+* :ghpull:`30345`: qt: Use better devicePixelRatio event to refresh scaling
+* :ghpull:`28554`: BLD: Enable wheels on Windows-on-ARM
+* :ghpull:`30292`: Backport PR #30237: Add explicit ``**options: Any`` for ``add_subplot`` m…
+* :ghpull:`29935`: Backport PR #29908 on branch v3.10.x (TST: Use text placeholders for empty legends)
+* :ghpull:`30327`: FIX Update Axes limits from Axes.add_collection(... autolim=True)
+* :ghpull:`30344`: Support fractional HiDPI in GTK4 backend
+* :ghpull:`30326`: Backport PR #30321 on branch v3.10.x (Fix type annotation for Axes.get_legend() to include None)
+* :ghpull:`30321`: Fix type annotation for Axes.get_legend() to include None
+* :ghpull:`30287`: Backport PR #30286 on branch v3.10.x (Fix whitespace in _axes.py error message)
+* :ghpull:`30288`: Backport PR #30283 on branch v3.10.x (changed the FAQ link to point to the correct path)
+* :ghpull:`30293`: Backport PR #30289 on branch v3.10.x (DOC: Fix build with pybind11 3)
+* :ghpull:`30283`: changed the FAQ link to point to the correct path
+* :ghpull:`30286`: Fix whitespace in _axes.py error message
+* :ghpull:`30271`: Reduce pause time in interactive timer test
+* :ghpull:`30269`: Backport PR #30186 on branch v3.10.x (Fix figure legend when drawing stackplots)
+* :ghpull:`30186`: Fix figure legend when drawing stackplots
+* :ghpull:`30268`: Backport PR #30233 on branch v3.10.x (Check that stem input is 1D)
+* :ghpull:`30233`: Check that stem input is 1D
+* :ghpull:`30259`: Backport PR #30256 on branch v3.10.x (Time out in _get_executable_info)
+* :ghpull:`30256`: Time out in _get_executable_info
+* :ghpull:`30237`: Add explicit ``**options: Any`` for ``add_subplot`` method
+* :ghpull:`30253`: Backport PR #30243 on branch v3.10.x (Fix FancyArrow rendering for zero-length arrows)
+* :ghpull:`30243`: Fix FancyArrow rendering for zero-length arrows
+* :ghpull:`30250`: Backport PR #30244 on branch v3.10.x (DOC: Recommend to use bare Figure instances for saving to file)
+* :ghpull:`30247`: Backport PR #30246 on branch v3.10.x (chore: remove redundant words in comment)
+* :ghpull:`30246`: chore: remove redundant words in comment
+* :ghpull:`30240`: Backport PR #30236 on branch v3.10.x (Copy-edit the docstring of AuxTransformBox.)
+* :ghpull:`30236`: Copy-edit the docstring of AuxTransformBox.
+* :ghpull:`30234`: Backport PR #30209 on branch v3.10.x (Clean up Qt socket notifier to avoid spurious interrupt handler calls)
+* :ghpull:`30209`: Clean up Qt socket notifier to avoid spurious interrupt handler calls
+* :ghpull:`30195`: ci: Enable wheel builds on Python 3.14
+* :ghpull:`30229`: Backport PR #30221 on branch v3.10.x (BUG: fix future incompatibility with Pillow 13)
+* :ghpull:`30221`: BUG: fix future incompatibility with Pillow 13
+* :ghpull:`30228`: Backport PR #30098 on branch v3.10.x (Fix label_outer in the presence of colorbars.)
+* :ghpull:`30227`: Backport PR #30223 on branch v3.10.x (Polar log scale: fix inner patch boundary and spine location)
+* :ghpull:`30098`: Fix label_outer in the presence of colorbars.
+* :ghpull:`30223`: Polar log scale: fix inner patch boundary and spine location
+* :ghpull:`30217`: Backport PR #30198 on branch v3.10.x (Implement Path.__deepcopy__ avoiding infinite recursion)
+* :ghpull:`30198`: Implement Path.__deepcopy__ avoiding infinite recursion
+* :ghpull:`30213`: Backport PR #30212 on branch v3.10.x ([Doc]: fix bug in release notes for matplotlib v3.5.0 and v3.7.0)
+* :ghpull:`30189`: Backport PR #30180 on branch v3.10.x (DOC: expand polar example)
+* :ghpull:`30167`: Backport PR #30162 on branch v3.10.x (TST: Fix runtime error checking NaN input to format_cursor_data)
+* :ghpull:`30162`: TST: Fix runtime error checking NaN input to format_cursor_data
+* :ghpull:`30146`: Backport PR #30144 on branch v3.10.x (js: Fix externally-controlled format strings)
+* :ghpull:`30144`: js: Fix externally-controlled format strings
+* :ghpull:`30140`: Backport PR #30118 on branch v3.10.x (CI: Skip jobs on forks)
+* :ghpull:`30120`: Backport PR #30114 on branch v3.10.x (Fix _is_tensorflow_array.)
+* :ghpull:`30122`: Backport PR #30119 on branch v3.10.x (Add some types to _mathtext.py)
+* :ghpull:`30119`: Add some types to _mathtext.py
+* :ghpull:`30114`: Fix _is_tensorflow_array.
+* :ghpull:`30106`: Backport PR #30089 on branch v3.10.x (FIX: fix submerged margins algorithm being applied twice)
+* :ghpull:`30089`: FIX: fix submerged margins algorithm being applied twice
+* :ghpull:`30101`: Backport PR #30096 on branch v3.10.x (Fix OffsetBox custom picker)
+* :ghpull:`30096`: Fix OffsetBox custom picker
+* :ghpull:`30081`: Backport PR #30079 on branch v3.10.x (FIX: cast legend handles to list)
+* :ghpull:`30079`: FIX: cast legend handles to list
+* :ghpull:`30057`: Backport PR #29895 on branch v3.10.x (The 'lines.markeredgecolor' now doesn't interfere on the color of errorbar caps)"
+* :ghpull:`29895`: The 'lines.markeredgecolor' now doesn't interfere on the color of errorbar caps
+* :ghpull:`30033`: Backport PR #30029 on branch v3.10.x (Update diagram in subplots_adjust documentation to clarify parameters)
+
+Issues (18):
+
+* :ghissue:`30370`: [Bug]: matplotlib simple example fails in Python 3.14
+* :ghissue:`30218`: [Bug]: Rendering on Wayland with fractional scaling looks bad
+* :ghissue:`30318`: [Bug]: type annotation of ``Axes.get_legend()`` misses ``None``
+* :ghissue:`30169`: [Doc]: Incorrect FAQ Link on Tutorials Page
+* :ghissue:`30285`: [Bug]: Missing whitespace in _axes.py error message
+* :ghissue:`30280`: [Bug]: Pillow 11.3 raises a deprecation warning when using TkAgg
+* :ghissue:`30158`: [Bug]: Stackplot in SubFigure raises when drawing Legend
+* :ghissue:`30216`: [Bug]: stem complaining about PyTorch's Tensor
+* :ghissue:`30242`: [Bug]: Cannot create empty FancyArrow (expired numpy deprecation)
+* :ghissue:`30249`: [Bug]: DeprecationWarning from Pillow 11.3.0 about 'mode' parameter of PIL.Image.fromarray()
+* :ghissue:`29688`: [Bug]: "Bad file descriptor" raised repeatedly when plt.pause() interrupted in IPython
+* :ghissue:`27305`: [Bug]: Axes.label_outer() does not work when there is a colorbar
+* :ghissue:`30179`: [Bug]: Inner border is not rendered correctly when using log-scale and polar projection.
+* :ghissue:`29157`: FUTURE BUG: reconsider how we deep-copy path objects
+* :ghissue:`30152`: [Bug]: Test pipeline failure on windows
+* :ghissue:`30076`: [Bug]: Layout Managers are confused by complex arrangement of sub-figures and gridspec's
+* :ghissue:`30078`: [Bug]: legend no longer works with itertools.chain
+* :ghissue:`29780`: [Bug]: Setting 'lines.markeredgecolor' affects color of errorbar caps.
diff --git a/doc/users/prev_whats_new/github_stats_3.2.0.rst b/doc/release/prev_whats_new/github_stats_3.2.0.rst
similarity index 99%
rename from doc/users/prev_whats_new/github_stats_3.2.0.rst
rename to doc/release/prev_whats_new/github_stats_3.2.0.rst
index 3cb3fce5de52..32151f0898a8 100644
--- a/doc/users/prev_whats_new/github_stats_3.2.0.rst
+++ b/doc/release/prev_whats_new/github_stats_3.2.0.rst
@@ -1,3 +1,5 @@
+.. redirect-from:: /users/prev_whats_new/github_stats_3.2.0
+
.. _github-stats-3-2-0:
GitHub statistics for 3.2.0 (Mar 04, 2020)
@@ -264,12 +266,12 @@ Pull Requests (839):
* :ghpull:`16112`: CI: Fail when failed to install dependencies
* :ghpull:`16119`: Backport PR #16065 on branch v3.2.x (Nicer formatting of community aspects on front page)
* :ghpull:`16074`: Backport PR #16061 on branch v3.2.x (Fix deprecation message for axes_grid1.colorbar.)
-* :ghpull:`16093`: Backport PR #16079 on branch v3.2.x (Fix restuctured text formatting)
+* :ghpull:`16093`: Backport PR #16079 on branch v3.2.x (Fix restructured text formatting)
* :ghpull:`16094`: Backport PR #16080 on branch v3.2.x (Cleanup docstrings in backend_bases.py)
* :ghpull:`16086`: FIX: use supported attribute to check pillow version
* :ghpull:`16084`: Backport PR #16077 on branch v3.2.x (Fix some typos)
* :ghpull:`16077`: Fix some typos
-* :ghpull:`16079`: Fix restuctured text formatting
+* :ghpull:`16079`: Fix restructured text formatting
* :ghpull:`16080`: Cleanup docstrings in backend_bases.py
* :ghpull:`16061`: Fix deprecation message for axes_grid1.colorbar.
* :ghpull:`16006`: Ignore pos in StrCategoryFormatter.__call__ to display correct label in the preview window
@@ -811,7 +813,7 @@ Pull Requests (839):
* :ghpull:`14310`: Update to Bounding Box for Qt5 FigureCanvasATAgg.paintEvent()
* :ghpull:`14380`: Inline $MPLLOCALFREETYPE/$PYTEST_ADDOPTS/$NPROC in .travis.yml.
* :ghpull:`14413`: MAINT: small improvements to the pdf backend
-* :ghpull:`14452`: MAINT: Minor cleanup to make functions more self consisntent
+* :ghpull:`14452`: MAINT: Minor cleanup to make functions more self consistent
* :ghpull:`14441`: Misc. docstring cleanups.
* :ghpull:`14440`: Interpolations example
* :ghpull:`14402`: Prefer ``mpl.get_data_path()``, and support Paths in FontProperties.
@@ -827,7 +829,7 @@ Pull Requests (839):
* :ghpull:`14311`: travis: add c code coverage measurements
* :ghpull:`14393`: Remove remaining unicode-strings markers.
* :ghpull:`14391`: Remove explicit inheritance from object
-* :ghpull:`14343`: acquiring and releaseing keypresslock when textbox is being activated
+* :ghpull:`14343`: acquiring and releasing keypresslock when textbox is being activated
* :ghpull:`14353`: Register flaky pytest marker.
* :ghpull:`14373`: Properly hide __has_include to support C++<17 compilers.
* :ghpull:`14378`: Remove setup_method
diff --git a/doc/users/prev_whats_new/github_stats_3.2.1.rst b/doc/release/prev_whats_new/github_stats_3.2.1.rst
similarity index 98%
rename from doc/users/prev_whats_new/github_stats_3.2.1.rst
rename to doc/release/prev_whats_new/github_stats_3.2.1.rst
index 4f865dbb5429..a6b2eb1bfe55 100644
--- a/doc/users/prev_whats_new/github_stats_3.2.1.rst
+++ b/doc/release/prev_whats_new/github_stats_3.2.1.rst
@@ -1,3 +1,5 @@
+.. redirect-from:: /users/prev_whats_new/github_stats_3.2.1
+
.. _github-stats-3-2-1:
GitHub statistics for 3.2.1 (Mar 18, 2020)
diff --git a/doc/users/prev_whats_new/github_stats_3.2.2.rst b/doc/release/prev_whats_new/github_stats_3.2.2.rst
similarity index 99%
rename from doc/users/prev_whats_new/github_stats_3.2.2.rst
rename to doc/release/prev_whats_new/github_stats_3.2.2.rst
index 9026d518ce4d..d6aae86d9b43 100644
--- a/doc/users/prev_whats_new/github_stats_3.2.2.rst
+++ b/doc/release/prev_whats_new/github_stats_3.2.2.rst
@@ -1,3 +1,5 @@
+.. redirect-from:: /users/prev_whats_new/github_stats_3.2.2
+
.. _github-stats-3-2-2:
GitHub statistics for 3.2.2 (Jun 17, 2020)
diff --git a/doc/users/prev_whats_new/github_stats_3.3.0.rst b/doc/release/prev_whats_new/github_stats_3.3.0.rst
similarity index 99%
rename from doc/users/prev_whats_new/github_stats_3.3.0.rst
rename to doc/release/prev_whats_new/github_stats_3.3.0.rst
index c2e6cd132c2d..47be96d0a2cb 100644
--- a/doc/users/prev_whats_new/github_stats_3.3.0.rst
+++ b/doc/release/prev_whats_new/github_stats_3.3.0.rst
@@ -1,3 +1,5 @@
+.. redirect-from:: /users/prev_whats_new/github_stats_3.3.0
+
.. _github-stats-3-3-0:
GitHub statistics for 3.3.0 (Jul 16, 2020)
@@ -254,7 +256,7 @@ Pull Requests (1066):
* :ghpull:`17617`: Rewrite pdf test to use check_figures_equal.
* :ghpull:`17654`: Small fixes to recent What's New
* :ghpull:`17649`: MNT: make _setattr_cm more forgiving
-* :ghpull:`17644`: Doc 33 whats new consolidation
+* :ghpull:`17644`: Doc 33 what's new consolidation
* :ghpull:`17647`: Fix example in docstring of cbook._unfold.
* :ghpull:`10187`: DOC: add a blitting tutorial
* :ghpull:`17471`: Removed idiomatic constructs from interactive figures docs
@@ -306,7 +308,7 @@ Pull Requests (1066):
* :ghpull:`17540`: Fix help window on GTK.
* :ghpull:`17535`: Update docs on subplot2grid / SubplotBase
* :ghpull:`17510`: Fix exception handling in FT2Font init.
-* :ghpull:`16953`: Changed 'colors' paramater in PyPlot vlines/hlines and Axes vlines/hlines to default to configured rcParams 'lines.color' option
+* :ghpull:`16953`: Changed 'colors' parameter in PyPlot vlines/hlines and Axes vlines/hlines to default to configured rcParams 'lines.color' option
* :ghpull:`17459`: Use light icons on dark themes for wx and gtk, too.
* :ghpull:`17539`: Use symbolic icons for buttons in GTK toolbar.
* :ghpull:`15435`: Reuse png metadata handling of imsave() in FigureCanvasAgg.print_png().
@@ -469,7 +471,7 @@ Pull Requests (1066):
* :ghpull:`15008`: ENH: add variable epoch
* :ghpull:`17260`: Text Rotation Example: Correct roation_mode typo
* :ghpull:`17258`: Improve info logged by tex subsystem.
-* :ghpull:`17211`: Deprecate support for running svg converter from path contaning newline.
+* :ghpull:`17211`: Deprecate support for running svg converter from path containing newline.
* :ghpull:`17078`: Improve nbAgg & WebAgg toolbars
* :ghpull:`17191`: Inline unsampled-image path; remove renderer kwarg from _check_unsampled_image.
* :ghpull:`17213`: Replace use of Bbox.bounds by appropriate properties.
@@ -604,7 +606,7 @@ Pull Requests (1066):
* :ghpull:`16823`: Dedupe implementation of axes grid switching in toolmanager.
* :ghpull:`16951`: Cleanup dates docstrings.
* :ghpull:`16769`: Fix some small style issues
-* :ghpull:`16936`: FIX: Plot is now rendered with correct inital value
+* :ghpull:`16936`: FIX: Plot is now rendered with correct initial value
* :ghpull:`16937`: Making sure to keep over/under/bad in cmap resample/reverse.
* :ghpull:`16915`: Tighten/cleanup wx backend.
* :ghpull:`16923`: Test the macosx backend on Travis.
@@ -860,7 +862,7 @@ Pull Requests (1066):
* :ghpull:`16439`: Rework pylab docstring.
* :ghpull:`16441`: Rework pylab docstring.
* :ghpull:`16442`: Expire deprecation of \stackrel.
-* :ghpull:`16365`: TST: test_acorr (replaced image comparison with figure comparion)
+* :ghpull:`16365`: TST: test_acorr (replaced image comparison with figure comparison)
* :ghpull:`16206`: Expire deprecation of \stackrel
* :ghpull:`16437`: Rework pylab docstring.
* :ghpull:`8896`: Fix mplot3d projection
@@ -898,7 +900,7 @@ Pull Requests (1066):
* :ghpull:`16304`: Simplify Legend.get_children.
* :ghpull:`16309`: Remove duplicated computations in Axes.get_tightbbox.
* :ghpull:`16314`: Avoid repeatedly warning about too many figures open.
-* :ghpull:`16319`: Put doc for XAxis befor YAxis and likewise for XTick, YTick.
+* :ghpull:`16319`: Put doc for XAxis before YAxis and likewise for XTick, YTick.
* :ghpull:`16313`: Cleanup constrainedlayout_guide.
* :ghpull:`16312`: Remove unnecessary Legend._approx_text_height.
* :ghpull:`16307`: Cleanup axes_demo.
@@ -991,7 +993,7 @@ Pull Requests (1066):
* :ghpull:`16078`: Refactor a bit animation start/save interaction.
* :ghpull:`16081`: Delay resolution of animation extra_args.
* :ghpull:`16088`: Use C++ true/false in ttconv.
-* :ghpull:`16082`: Defaut to writing animation frames to a temporary directory.
+* :ghpull:`16082`: Default to writing animation frames to a temporary directory.
* :ghpull:`16070`: Make animation blit cache robust against 3d viewpoint changes.
* :ghpull:`5056`: MNT: more control of colorbar with CountourSet
* :ghpull:`16051`: Deprecate parameters to colorbar which have no effect.
@@ -1133,7 +1135,7 @@ Pull Requests (1066):
* :ghpull:`15645`: Bump minimal numpy version to 1.12.
* :ghpull:`15646`: Hide sphinx-gallery config comments
* :ghpull:`15642`: Remove interpolation="nearest" from most examples.
-* :ghpull:`15671`: Don't mention tcl in tkagg commments anymore.
+* :ghpull:`15671`: Don't mention tcl in tkagg comments anymore.
* :ghpull:`15607`: Simplify tk loader.
* :ghpull:`15651`: Simplify axes_pad handling in axes_grid.
* :ghpull:`15652`: Remove mention of Enthought Canopy from the docs.
@@ -1400,7 +1402,7 @@ Issues (198):
* :ghissue:`16299`: The interactive polar plot animation's axis label won't scale.
* :ghissue:`15182`: More tests ``ConciseDateFormatter`` needed
* :ghissue:`16140`: Unclear Documentation for get_xticklabels
-* :ghissue:`16147`: pp.hist parmeter 'density' does not scale data appropriately
+* :ghissue:`16147`: pp.hist parameter 'density' does not scale data appropriately
* :ghissue:`16069`: matplotlib glitch when rotating interactively a 3d animation
* :ghissue:`14603`: Scatterplot: should vmin/vmax be ignored when a norm is specified?
* :ghissue:`15730`: Setting lines.marker = s in matplotlibrc also sets markers in boxplots
@@ -1423,7 +1425,7 @@ Issues (198):
* :ghissue:`15089`: Coerce MxNx1 images into MxN images for imshow
* :ghissue:`5253`: abline() - for drawing arbitrary lines on a plot, given specifications.
* :ghissue:`15165`: Switch to requiring Pillow rather than having our own png wrapper?
-* :ghissue:`15280`: Add pull request checklist to Reviewers Guidlines
+* :ghissue:`15280`: Add pull request checklist to Reviewers Guidelines
* :ghissue:`15289`: cbook.warn_deprecated() should warn with a MatplotlibDeprecationWarning not a UserWarning
* :ghissue:`15285`: DOC: make copy right year auto-update
* :ghissue:`15059`: fig.add_axes() with no arguments silently does nothing
diff --git a/doc/users/prev_whats_new/github_stats_3.3.1.rst b/doc/release/prev_whats_new/github_stats_3.3.1.rst
similarity index 98%
rename from doc/users/prev_whats_new/github_stats_3.3.1.rst
rename to doc/release/prev_whats_new/github_stats_3.3.1.rst
index 3fa2d39a4d90..24df6db38776 100644
--- a/doc/users/prev_whats_new/github_stats_3.3.1.rst
+++ b/doc/release/prev_whats_new/github_stats_3.3.1.rst
@@ -1,3 +1,5 @@
+.. redirect-from:: /users/prev_whats_new/github_stats_3.3.1
+
.. _github-stats-3-3-1:
GitHub statistics for 3.3.1 (Aug 13, 2020)
@@ -102,16 +104,16 @@ Pull Requests (73):
* :ghpull:`17995`: Avoid using Bbox machinery in Path.get_extents; special case polylines.
* :ghpull:`17994`: Special case degree-1 Bezier curves.
* :ghpull:`17990`: Manual backport of pr 17983 on v3.3.x
-* :ghpull:`17984`: Backport PR #17972 on branch v3.3.x (Fix PyPy compatiblity issue)
+* :ghpull:`17984`: Backport PR #17972 on branch v3.3.x (Fix PyPy compatibility issue)
* :ghpull:`17985`: Backport PR #17976 on branch v3.3.x (Fixed #17970 - Docstrings should not accessed with -OO)
* :ghpull:`17983`: FIX: undeprecate and update num2epoch/epoch2num
* :ghpull:`17976`: Fixed #17970 - Docstrings should not accessed with -OO
-* :ghpull:`17972`: Fix PyPy compatiblity issue
+* :ghpull:`17972`: Fix PyPy compatibility issue
Issues (25):
* :ghissue:`18234`: _reshape_2D function behavior changed, breaks hist for some cases in 3.3.0
-* :ghissue:`18232`: different behaviour between 3.3.0 and 3.2.2 (and earlier) for ploting in a Tk canvas
+* :ghissue:`18232`: different behaviour between 3.3.0 and 3.2.2 (and earlier) for plotting in a Tk canvas
* :ghissue:`18212`: Updated WxAgg NavigationToolbar2 breaks custom toolbars
* :ghissue:`18129`: Error reading png image from URL with imread in matplotlib 3.3
* :ghissue:`18163`: Figure cannot be closed if it has associated Agg canvas
diff --git a/doc/users/prev_whats_new/github_stats_3.3.2.rst b/doc/release/prev_whats_new/github_stats_3.3.2.rst
similarity index 98%
rename from doc/users/prev_whats_new/github_stats_3.3.2.rst
rename to doc/release/prev_whats_new/github_stats_3.3.2.rst
index 0bc03cbc83ee..8875a51c830f 100644
--- a/doc/users/prev_whats_new/github_stats_3.3.2.rst
+++ b/doc/release/prev_whats_new/github_stats_3.3.2.rst
@@ -1,3 +1,5 @@
+.. redirect-from:: /users/prev_whats_new/github_stats_3.3.2
+
.. _github-stats-3-3-2:
GitHub statistics for 3.3.2 (Sep 15, 2020)
diff --git a/doc/users/prev_whats_new/github_stats_3.3.3.rst b/doc/release/prev_whats_new/github_stats_3.3.3.rst
similarity index 98%
rename from doc/users/prev_whats_new/github_stats_3.3.3.rst
rename to doc/release/prev_whats_new/github_stats_3.3.3.rst
index 5475a5209eed..dc8f9964c30f 100644
--- a/doc/users/prev_whats_new/github_stats_3.3.3.rst
+++ b/doc/release/prev_whats_new/github_stats_3.3.3.rst
@@ -1,3 +1,5 @@
+.. redirect-from:: /users/prev_whats_new/github_stats_3.3.3
+
.. _github-stats-3-3-3:
GitHub statistics for 3.3.3 (Nov 11, 2020)
diff --git a/doc/users/prev_whats_new/github_stats_3.3.4.rst b/doc/release/prev_whats_new/github_stats_3.3.4.rst
similarity index 97%
rename from doc/users/prev_whats_new/github_stats_3.3.4.rst
rename to doc/release/prev_whats_new/github_stats_3.3.4.rst
index afff8b384b8e..23fcf6fe0da3 100644
--- a/doc/users/prev_whats_new/github_stats_3.3.4.rst
+++ b/doc/release/prev_whats_new/github_stats_3.3.4.rst
@@ -1,3 +1,5 @@
+.. redirect-from:: /users/prev_whats_new/github_stats_3.3.4
+
.. _github-stats-3-3-4:
GitHub statistics for 3.3.4 (Jan 28, 2021)
diff --git a/doc/users/prev_whats_new/github_stats_3.4.0.rst b/doc/release/prev_whats_new/github_stats_3.4.0.rst
similarity index 98%
rename from doc/users/prev_whats_new/github_stats_3.4.0.rst
rename to doc/release/prev_whats_new/github_stats_3.4.0.rst
index b2568058b455..c6cc8f7091d6 100644
--- a/doc/users/prev_whats_new/github_stats_3.4.0.rst
+++ b/doc/release/prev_whats_new/github_stats_3.4.0.rst
@@ -1,3 +1,5 @@
+.. redirect-from:: /users/prev_whats_new/github_stats_3.4.0
+
.. _github-stats-3-4-0:
GitHub statistics for 3.4.0 (Mar 26, 2021)
@@ -426,9 +428,9 @@ Pull Requests (772):
* :ghpull:`19207`: Fix Grouper example code
* :ghpull:`19204`: Clarify Date Format Example
* :ghpull:`19200`: Fix incorrect statement regarding test images cache size.
-* :ghpull:`19198`: Fix link in contrbuting docs
+* :ghpull:`19198`: Fix link in contributing docs
* :ghpull:`19196`: Fix PR welcome action
-* :ghpull:`19188`: Cleanup comparision between X11/CSS4 and xkcd colors
+* :ghpull:`19188`: Cleanup comparison between X11/CSS4 and xkcd colors
* :ghpull:`19194`: Fix trivial quiver doc typo.
* :ghpull:`19180`: Fix Artist.remove_callback()
* :ghpull:`19192`: Fixed part of Issue - #19100, changed documentation for axisartist
@@ -472,7 +474,7 @@ Pull Requests (772):
* :ghpull:`19127`: Cleanups to webagg & friends.
* :ghpull:`19122`: FIX/DOC - make Text doscstring interp more easily searchable
* :ghpull:`19106`: Support setting rcParams["image.cmap"] to Colormap instances.
-* :ghpull:`19085`: FIX: update a transfrom from transFigure to transSubfigure
+* :ghpull:`19085`: FIX: update a transform from transFigure to transSubfigure
* :ghpull:`19117`: Rename a confusing variable.
* :ghpull:`18647`: Axes.axline: implement support transform argument (for points but not slope)
* :ghpull:`16220`: Fix interaction with unpickled 3d plots.
@@ -701,7 +703,7 @@ Pull Requests (772):
* :ghpull:`18564`: Prepare for merging SubplotBase into AxesBase.
* :ghpull:`15127`: ENH/API: improvements to register_cmap
* :ghpull:`18576`: DOC: prefer colormap over color map
-* :ghpull:`18340`: Colorbar grid postion
+* :ghpull:`18340`: Colorbar grid position
* :ghpull:`18568`: Added Reporting to code_of_conduct.md
* :ghpull:`18555`: Convert _math_style_dict into an Enum.
* :ghpull:`18567`: Replace subplot(ijk) calls by subplots(i, j)
@@ -759,7 +761,7 @@ Pull Requests (772):
* :ghpull:`18449`: Remove the private Axes._set_position.
* :ghpull:`18460`: DOC: example gray level in 'Specifying Colors' tutorial
* :ghpull:`18426`: plot directive: caption-option
-* :ghpull:`18444`: Support doubleclick in webagg/nbagg
+* :ghpull:`18444`: Support double-click in webagg/nbagg
* :ghpull:`12518`: Example showing scale-invariant angle arc
* :ghpull:`18446`: Normalize properties passed to ToolHandles.
* :ghpull:`18445`: Warn if an animation is gc'd before doing anything.
@@ -808,9 +810,9 @@ Pull Requests (772):
* :ghpull:`17901`: DOC: Autoreformating of backend/\*.py
* :ghpull:`17291`: Normalize gridspec ratios to lists in the setter.
* :ghpull:`18226`: Use CallbackRegistry in Widgets and some related cleanup
-* :ghpull:`18203`: Force locator and formatter inheritence
+* :ghpull:`18203`: Force locator and formatter inheritance
* :ghpull:`18279`: boxplot: Add conf_intervals reference to notch docs.
-* :ghpull:`18276`: Fix autoscaling to exclude inifinite data limits when possible.
+* :ghpull:`18276`: Fix autoscaling to exclude infinite data limits when possible.
* :ghpull:`18261`: Migrate tk backend tests into subprocesses
* :ghpull:`17961`: DOCS: Remove How-to: Contributing
* :ghpull:`18201`: Remove mpl.colors deprecations for 3.4
@@ -964,7 +966,7 @@ Pull Requests (772):
* :ghpull:`17697`: Add description examples/pyplots/pyplot simple.py
* :ghpull:`17694`: CI: Only skip devdocs deploy if PR is to this repo.
* :ghpull:`17691`: ci: Print out reasons for not deploying docs.
-* :ghpull:`17099`: Make Spines accessable by the attributes.
+* :ghpull:`17099`: Make Spines accessible by the attributes
Issues (204):
@@ -1044,7 +1046,7 @@ Issues (204):
* :ghissue:`19099`: axisartist axis_direction bug
* :ghissue:`19171`: 3D surface example bug for non-square grid
* :ghissue:`18112`: set_{x,y,z}bound 3d limits are not persistent upon interactive rotation
-* :ghissue:`19078`: _update_patch_limits should not use CLOSEPOLY verticies for updating
+* :ghissue:`19078`: _update_patch_limits should not use CLOSEPOLY vertices for updating
* :ghissue:`16123`: test_dpi_ratio_change fails on Windows/Qt5Agg
* :ghissue:`15796`: [DOC] PDF build of matplotlib own documentation crashes with LaTeX error "too deeply nested"
* :ghissue:`19091`: 3D Axes don't work in SubFigures
@@ -1091,13 +1093,13 @@ Issues (204):
* :ghissue:`18641`: Conversion cache cleaning is broken with xdist
* :ghissue:`15614`: named color examples need borders
* :ghissue:`5519`: The linestyle 'None', ' ' and '' not supported by PathPatch.
-* :ghissue:`17487`: Polygon selector with useblit=True - polygon dissapears
+* :ghissue:`17487`: Polygon selector with useblit=True - polygon disappears
* :ghissue:`17476`: RectangleSelector fails to clear itself after being toggled inactive and then back to active.
* :ghissue:`18600`: plt.errorbar raises error when given marker=
* :ghissue:`18355`: Optional components required to build docs aren't documented
* :ghissue:`18428`: small bug in the mtplotlib gallery
* :ghissue:`4438`: inconsistent behaviour of the errorevery option in pyplot.errorbar() to the markevery keyword
-* :ghissue:`5823`: pleas dont include the Google Analytics tracking in the off-line doc
+* :ghissue:`5823`: please don't include the Google Analytics tracking in the off-line doc
* :ghissue:`13035`: Path3DCollection from 3D scatter cannot set_color
* :ghissue:`9725`: scatter - set_facecolors is not working on Axes3D
* :ghissue:`3370`: Patch3DCollection doesn't update color after calling set_color
@@ -1123,12 +1125,12 @@ Issues (204):
* :ghissue:`17712`: constrained_layout fails on suptitle+colorbars+some figure sizes
* :ghissue:`14638`: colorbar.make_axes doesn't anchor in constrained_layout
* :ghissue:`18299`: New configure_subplots behaves badly on TkAgg backend
-* :ghissue:`18300`: Remove the examples category "Our Favorite Recipies"
+* :ghissue:`18300`: Remove the examples category "Our Favorite Recipes"
* :ghissue:`18077`: Imshow breaks if given a unyt_array input
* :ghissue:`7074`: Using a linestyle cycler with plt.errorbar results in strange plots
* :ghissue:`18236`: FuncAnimation fails to display with interval 0 on Tkagg backend
* :ghissue:`8107`: invalid command name "..._on_timer" in FuncAnimation for (too) small interval
-* :ghissue:`18272`: Add CI Intervall to boxplot notch documentation
+* :ghissue:`18272`: Add CI Interval to boxplot notch documentation
* :ghissue:`18137`: axhspan() in empty plots changes the xlimits of plots sharing the X axis
* :ghissue:`18246`: test_never_update is flaky
* :ghissue:`5856`: Horizontal stem plot
@@ -1146,7 +1148,7 @@ Issues (204):
* :ghissue:`12198`: axvline incorrectly tries to handle unitized ymin, ymax
* :ghissue:`9139`: Python3 matplotlib 2.0.2 with Times New Roman misses unicode minus sign in pdf
* :ghissue:`5970`: pyplot.scatter raises obscure error when mistakenly passed a third string param
-* :ghissue:`17936`: documenattion and behavior do not match for suppressing (PDF) metadata
+* :ghissue:`17936`: documentation and behavior do not match for suppressing (PDF) metadata
* :ghissue:`17932`: latex textrm does not work in Cairo backend
* :ghissue:`17714`: Universal fullscreen command
* :ghissue:`4584`: ColorbarBase draws edges in slightly wrong positions.
@@ -1161,7 +1163,7 @@ Issues (204):
* :ghissue:`15821`: Should constrained_layout work as plt.figure() argument?
* :ghissue:`15616`: Colormaps should have a ``_repr_html_`` that is an image of the colormap
* :ghissue:`17579`: ``BoundaryNorm`` yield a ``ZeroDivisionError: division by zero``
-* :ghissue:`17652`: NEP 29 : Stop support fro Python 3.6 soon ?
+* :ghissue:`17652`: NEP 29 : Stop support for Python 3.6 soon ?
* :ghissue:`11095`: Repeated plot calls with xunits=None throws exception
* :ghissue:`17733`: Rename "array" (and perhaps "fields") section of Axes API
* :ghissue:`15610`: Link to most recent DevDocs when installing from Master Source
diff --git a/doc/users/prev_whats_new/github_stats_3.4.1.rst b/doc/release/prev_whats_new/github_stats_3.4.1.rst
similarity index 97%
rename from doc/users/prev_whats_new/github_stats_3.4.1.rst
rename to doc/release/prev_whats_new/github_stats_3.4.1.rst
index 0819a6850a3e..5452bfd15349 100644
--- a/doc/users/prev_whats_new/github_stats_3.4.1.rst
+++ b/doc/release/prev_whats_new/github_stats_3.4.1.rst
@@ -1,3 +1,5 @@
+.. redirect-from:: /users/prev_whats_new/github_stats_3.4.1
+
.. _github-stats-3-4-1:
GitHub statistics for 3.4.1 (Mar 31, 2021)
diff --git a/doc/users/prev_whats_new/github_stats_3.4.2.rst b/doc/release/prev_whats_new/github_stats_3.4.2.rst
similarity index 98%
rename from doc/users/prev_whats_new/github_stats_3.4.2.rst
rename to doc/release/prev_whats_new/github_stats_3.4.2.rst
index 22b4797c2fc2..4d5e13e9a587 100644
--- a/doc/users/prev_whats_new/github_stats_3.4.2.rst
+++ b/doc/release/prev_whats_new/github_stats_3.4.2.rst
@@ -1,3 +1,5 @@
+.. redirect-from:: /users/prev_whats_new/github_stats_3.4.2
+
.. _github-stats-3-4-2:
GitHub statistics for 3.4.2 (May 08, 2021)
@@ -146,7 +148,7 @@ Issues (21):
* :ghissue:`19960`: Failed to init RangeSlider with valinit attribute
* :ghissue:`19736`: subplot_mosaic axes are not added in consistent order
* :ghissue:`19979`: Blank EPS figures if plot contains 'd'
-* :ghissue:`19938`: unuseful deprecation warning figbox
+* :ghissue:`19938`: useless deprecation warning figbox
* :ghissue:`19958`: subfigures missing bbox_inches attribute in inline backend
* :ghissue:`19936`: Errorbars elinewidth raise error when numpy array
* :ghissue:`19879`: Using "drawstyle" raises AttributeError in errorbar, when yerr is specified.
diff --git a/doc/users/prev_whats_new/github_stats_3.4.3.rst b/doc/release/prev_whats_new/github_stats_3.4.3.rst
similarity index 98%
rename from doc/users/prev_whats_new/github_stats_3.4.3.rst
rename to doc/release/prev_whats_new/github_stats_3.4.3.rst
index b248bf69b6ef..9256b20b1e16 100644
--- a/doc/users/prev_whats_new/github_stats_3.4.3.rst
+++ b/doc/release/prev_whats_new/github_stats_3.4.3.rst
@@ -1,3 +1,5 @@
+.. redirect-from:: /users/prev_whats_new/github_stats_3.4.3
+
.. _github-stats-3-4-3:
GitHub statistics for 3.4.3 (August 21, 2021)
@@ -119,7 +121,7 @@ Issues (22):
* :ghissue:`20628`: Out-of-bounds read leads to crash or broken TrueType fonts
* :ghissue:`20612`: Broken EPS for Type 42 STIX
* :ghissue:`19982`: regression for 3.4.x - ax.figbox replacement incompatible to all version including 3.3.4
-* :ghissue:`19938`: unuseful deprecation warning figbox
+* :ghissue:`19938`: useless deprecation warning figbox
* :ghissue:`16400`: Inconsistent behavior between Normalizers when input is Dataframe
* :ghissue:`20583`: Lost class descriptions since 3.4 docs
* :ghissue:`20551`: set_segments(get_segments()) makes lines coarse
diff --git a/doc/users/prev_whats_new/github_stats_3.5.0.rst b/doc/release/prev_whats_new/github_stats_3.5.0.rst
similarity index 99%
rename from doc/users/prev_whats_new/github_stats_3.5.0.rst
rename to doc/release/prev_whats_new/github_stats_3.5.0.rst
index bde4d917b38b..89a58de096a1 100644
--- a/doc/users/prev_whats_new/github_stats_3.5.0.rst
+++ b/doc/release/prev_whats_new/github_stats_3.5.0.rst
@@ -1,3 +1,5 @@
+.. redirect-from:: /users/prev_whats_new/github_stats_3.5.0
+
.. _github-stats-3-5-0:
GitHub statistics for 3.5.0 (Nov 15, 2021)
@@ -1121,7 +1123,7 @@ Issues (187):
* :ghissue:`20847`: [Bug]: Contourf not filling contours.
* :ghissue:`21300`: [Bug]: zooming in on contour plot gives false extra contour lines
* :ghissue:`21466`: [Bug]: EPS export shows hidden tick labels when using tex for text rendering
-* :ghissue:`21463`: [Bug]: Plotting lables with Greek latters in math mode produces Parsing error when plt.show() runs
+* :ghissue:`21463`: [Bug]: Plotting labels with Greek latters in math mode produces Parsing error when plt.show() runs
* :ghissue:`20534`: Document formatting for sections
* :ghissue:`21246`: [Doc]: Install info takes up too much room on new front page
* :ghissue:`21432`: [Doc]: Double clicking parameter name also highlights next item of text
@@ -1157,7 +1159,7 @@ Issues (187):
* :ghissue:`16251`: API changes are too hard to find in the rendered docs
* :ghissue:`20770`: [Doc]: How to replicate behaviour of ``plt.gca(projection=...)``?
* :ghissue:`17052`: Colorbar update error with clim change in multi_image.py example
-* :ghissue:`4387`: make ``Normalize`` objects notifiy scalar-mappables on changes
+* :ghissue:`4387`: make ``Normalize`` objects notify scalar-mappables on changes
* :ghissue:`20001`: rename fig.draw_no_output
* :ghissue:`20936`: [Bug]: edgecolor 'auto' doesn't work properly
* :ghissue:`20909`: [Bug]: Animation error message
@@ -1241,7 +1243,7 @@ Issues (187):
* :ghissue:`17508`: Quadmesh.set_array should validate dimensions
* :ghissue:`20372`: Incorrect axes positioning in axes_grid.Grid with direction='column'
* :ghissue:`19419`: Dev version hard to check
-* :ghissue:`17310`: Matplotlib git master version fails to pass serveral pytest's tests.
+* :ghissue:`17310`: Matplotlib git master version fails to pass several pytest's tests
* :ghissue:`7742`: plot_date() after axhline() doesn't rescale axes
* :ghissue:`20322`: QuadMesh default for shading inadvertently changed.
* :ghissue:`9653`: SVG savefig + LaTeX extremely slow on macOS
diff --git a/doc/users/prev_whats_new/github_stats_3.5.1.rst b/doc/release/prev_whats_new/github_stats_3.5.1.rst
similarity index 98%
rename from doc/users/prev_whats_new/github_stats_3.5.1.rst
rename to doc/release/prev_whats_new/github_stats_3.5.1.rst
index 7eb37b769d6c..dba71812c76c 100644
--- a/doc/users/prev_whats_new/github_stats_3.5.1.rst
+++ b/doc/release/prev_whats_new/github_stats_3.5.1.rst
@@ -1,3 +1,5 @@
+.. redirect-from:: /users/prev_whats_new/github_stats_3.5.1
+
.. _github-stats-3-5-1:
GitHub statistics for 3.5.1 (Dec 11, 2021)
@@ -131,7 +133,7 @@ Issues (29):
* :ghissue:`21803`: [Bug]: using ``set_offsets`` on scatter object raises TypeError
* :ghissue:`21839`: [Bug]: Top of plot clipped when using Subfigures without suptitle
* :ghissue:`21841`: [Bug]: Wrong tick labels and colorbar of discrete normalizer
-* :ghissue:`21783`: [MNT]: wheel of 3.5.0 apears to depend on setuptools-scm which apears to be unintentional
+* :ghissue:`21783`: [MNT]: wheel of 3.5.0 appears to depend on setuptools-scm which appears to be unintentional
* :ghissue:`21733`: [Bug]: Possible bug on arrows in annotation
* :ghissue:`21749`: [Bug]: Regression on ``tight_layout`` when manually adding axes for colorbars
* :ghissue:`19197`: Unexpected error after using Figure.canvas.draw on macosx backend
diff --git a/doc/users/prev_whats_new/github_stats_3.5.2.rst b/doc/release/prev_whats_new/github_stats_3.5.2.rst
similarity index 99%
rename from doc/users/prev_whats_new/github_stats_3.5.2.rst
rename to doc/release/prev_whats_new/github_stats_3.5.2.rst
index 66f53d8e3672..15e5067de7a6 100644
--- a/doc/users/prev_whats_new/github_stats_3.5.2.rst
+++ b/doc/release/prev_whats_new/github_stats_3.5.2.rst
@@ -1,3 +1,5 @@
+.. redirect-from:: /users/prev_whats_new/github_stats_3.5.2
+
.. _github-stats-3-5-2:
GitHub statistics for 3.5.2 (May 02, 2022)
diff --git a/doc/users/prev_whats_new/github_stats_3.5.3.rst b/doc/release/prev_whats_new/github_stats_3.5.3.rst
similarity index 99%
rename from doc/users/prev_whats_new/github_stats_3.5.3.rst
rename to doc/release/prev_whats_new/github_stats_3.5.3.rst
index bafd6d5c27eb..e8c22ccc63a2 100644
--- a/doc/users/prev_whats_new/github_stats_3.5.3.rst
+++ b/doc/release/prev_whats_new/github_stats_3.5.3.rst
@@ -1,3 +1,5 @@
+.. redirect-from:: /users/prev_whats_new/github_stats_3.5.3
+
.. _github-stats-3-5-3:
GitHub statistics for 3.5.3 (Aug 10, 2022)
diff --git a/doc/users/prev_whats_new/github_stats_3.6.0.rst b/doc/release/prev_whats_new/github_stats_3.6.0.rst
similarity index 99%
rename from doc/users/prev_whats_new/github_stats_3.6.0.rst
rename to doc/release/prev_whats_new/github_stats_3.6.0.rst
index 6764c7817741..32f373c13c78 100644
--- a/doc/users/prev_whats_new/github_stats_3.6.0.rst
+++ b/doc/release/prev_whats_new/github_stats_3.6.0.rst
@@ -1,3 +1,5 @@
+.. redirect-from:: /users/prev_whats_new/github_stats_3.6.0
+
.. _github-stats-3-6-0:
GitHub statistics for 3.6.0 (Sep 15, 2022)
diff --git a/doc/users/prev_whats_new/github_stats_3.6.1.rst b/doc/release/prev_whats_new/github_stats_3.6.1.rst
similarity index 99%
rename from doc/users/prev_whats_new/github_stats_3.6.1.rst
rename to doc/release/prev_whats_new/github_stats_3.6.1.rst
index d47dc28fa076..94167402172f 100644
--- a/doc/users/prev_whats_new/github_stats_3.6.1.rst
+++ b/doc/release/prev_whats_new/github_stats_3.6.1.rst
@@ -1,3 +1,5 @@
+.. redirect-from:: /users/prev_whats_new/github_stats_3.6.1
+
.. _github-stats-3-6-1:
GitHub statistics for 3.6.1 (Oct 08, 2022)
diff --git a/doc/users/prev_whats_new/github_stats_3.6.2.rst b/doc/release/prev_whats_new/github_stats_3.6.2.rst
similarity index 99%
rename from doc/users/prev_whats_new/github_stats_3.6.2.rst
rename to doc/release/prev_whats_new/github_stats_3.6.2.rst
index f633448aeaf1..e3b75268c528 100644
--- a/doc/users/prev_whats_new/github_stats_3.6.2.rst
+++ b/doc/release/prev_whats_new/github_stats_3.6.2.rst
@@ -1,3 +1,5 @@
+.. redirect-from:: /users/prev_whats_new/github_stats_3.6.2
+
.. _github-stats-3-6-2:
GitHub statistics for 3.6.2 (Nov 02, 2022)
diff --git a/doc/users/prev_whats_new/github_stats_3.6.3.rst b/doc/release/prev_whats_new/github_stats_3.6.3.rst
similarity index 99%
rename from doc/users/prev_whats_new/github_stats_3.6.3.rst
rename to doc/release/prev_whats_new/github_stats_3.6.3.rst
index b1d17a791c87..edd3f63c38c5 100644
--- a/doc/users/prev_whats_new/github_stats_3.6.3.rst
+++ b/doc/release/prev_whats_new/github_stats_3.6.3.rst
@@ -1,3 +1,5 @@
+.. redirect-from:: /users/prev_whats_new/github_stats_3.6.3
+
.. _github-stats-3-6-3:
GitHub statistics for 3.6.3 (Jan 11, 2023)
diff --git a/doc/users/prev_whats_new/github_stats_3.7.0.rst b/doc/release/prev_whats_new/github_stats_3.7.0.rst
similarity index 99%
rename from doc/users/prev_whats_new/github_stats_3.7.0.rst
rename to doc/release/prev_whats_new/github_stats_3.7.0.rst
index 754c4c1fc059..88140a2bc021 100644
--- a/doc/users/prev_whats_new/github_stats_3.7.0.rst
+++ b/doc/release/prev_whats_new/github_stats_3.7.0.rst
@@ -1,3 +1,5 @@
+.. redirect-from:: /users/prev_whats_new/github_stats_3.7.0
+
.. _github-stats-3-7-0:
GitHub statistics for 3.7.0 (Feb 13, 2023)
diff --git a/doc/users/prev_whats_new/github_stats_3.7.1.rst b/doc/release/prev_whats_new/github_stats_3.7.1.rst
similarity index 99%
rename from doc/users/prev_whats_new/github_stats_3.7.1.rst
rename to doc/release/prev_whats_new/github_stats_3.7.1.rst
index b187122cb779..9147ff4e6ac1 100644
--- a/doc/users/prev_whats_new/github_stats_3.7.1.rst
+++ b/doc/release/prev_whats_new/github_stats_3.7.1.rst
@@ -1,3 +1,5 @@
+.. redirect-from:: /users/prev_whats_new/github_stats_3.7.1
+
.. _github-stats-3-7-1:
GitHub statistics for 3.7.1 (Mar 03, 2023)
diff --git a/doc/users/prev_whats_new/github_stats_3.7.2.rst b/doc/release/prev_whats_new/github_stats_3.7.2.rst
similarity index 99%
rename from doc/users/prev_whats_new/github_stats_3.7.2.rst
rename to doc/release/prev_whats_new/github_stats_3.7.2.rst
index 9bc8ab85fdfd..500ce807f524 100644
--- a/doc/users/prev_whats_new/github_stats_3.7.2.rst
+++ b/doc/release/prev_whats_new/github_stats_3.7.2.rst
@@ -1,3 +1,5 @@
+.. redirect-from:: /users/prev_whats_new/github_stats_3.7.2
+
.. _github-stats-3-7-2:
GitHub statistics for 3.7.2 (Jul 05, 2023)
diff --git a/doc/users/prev_whats_new/github_stats_3.7.3.rst b/doc/release/prev_whats_new/github_stats_3.7.3.rst
similarity index 98%
rename from doc/users/prev_whats_new/github_stats_3.7.3.rst
rename to doc/release/prev_whats_new/github_stats_3.7.3.rst
index bb43c1a8395e..f5b0ab256996 100644
--- a/doc/users/prev_whats_new/github_stats_3.7.3.rst
+++ b/doc/release/prev_whats_new/github_stats_3.7.3.rst
@@ -1,3 +1,5 @@
+.. redirect-from:: /users/prev_whats_new/github_stats_3.7.3
+
.. _github-stats-3-7-3:
GitHub statistics for 3.7.3 (Sep 11, 2023)
diff --git a/doc/users/prev_whats_new/github_stats_3.8.0.rst b/doc/release/prev_whats_new/github_stats_3.8.0.rst
similarity index 99%
rename from doc/users/prev_whats_new/github_stats_3.8.0.rst
rename to doc/release/prev_whats_new/github_stats_3.8.0.rst
index 219e60f399ac..589093455af5 100644
--- a/doc/users/prev_whats_new/github_stats_3.8.0.rst
+++ b/doc/release/prev_whats_new/github_stats_3.8.0.rst
@@ -1,3 +1,5 @@
+.. redirect-from:: /users/prev_whats_new/github_stats_3.8.0
+
.. _github-stats-3-8-0:
GitHub statistics for 3.8.0 (Sep 14, 2023)
diff --git a/doc/users/prev_whats_new/github_stats_3.8.1.rst b/doc/release/prev_whats_new/github_stats_3.8.1.rst
similarity index 99%
rename from doc/users/prev_whats_new/github_stats_3.8.1.rst
rename to doc/release/prev_whats_new/github_stats_3.8.1.rst
index 86de0e3b70a9..5043f5b641f1 100644
--- a/doc/users/prev_whats_new/github_stats_3.8.1.rst
+++ b/doc/release/prev_whats_new/github_stats_3.8.1.rst
@@ -1,3 +1,5 @@
+.. redirect-from:: /users/prev_whats_new/github_stats_3.8.1
+
.. _github-stats-3-8-1:
GitHub statistics for 3.8.1 (Oct 31, 2023)
diff --git a/doc/users/prev_whats_new/github_stats_3.8.2.rst b/doc/release/prev_whats_new/github_stats_3.8.2.rst
similarity index 98%
rename from doc/users/prev_whats_new/github_stats_3.8.2.rst
rename to doc/release/prev_whats_new/github_stats_3.8.2.rst
index 0e5852be394b..1703c2e1bbb4 100644
--- a/doc/users/prev_whats_new/github_stats_3.8.2.rst
+++ b/doc/release/prev_whats_new/github_stats_3.8.2.rst
@@ -1,3 +1,5 @@
+.. redirect-from:: /users/prev_whats_new/github_stats_3.8.2
+
.. _github-stats-3-8-2:
GitHub statistics for 3.8.2 (Nov 17, 2023)
diff --git a/doc/users/prev_whats_new/github_stats_3.8.3.rst b/doc/release/prev_whats_new/github_stats_3.8.3.rst
similarity index 99%
rename from doc/users/prev_whats_new/github_stats_3.8.3.rst
rename to doc/release/prev_whats_new/github_stats_3.8.3.rst
index c91e046fd6ae..c43a215bb869 100644
--- a/doc/users/prev_whats_new/github_stats_3.8.3.rst
+++ b/doc/release/prev_whats_new/github_stats_3.8.3.rst
@@ -1,3 +1,5 @@
+.. redirect-from:: /users/prev_whats_new/github_stats_3.8.3
+
.. _github-stats-3-8-3:
GitHub statistics for 3.8.3 (Feb 14, 2024)
diff --git a/doc/users/prev_whats_new/github_stats_3.8.4.rst b/doc/release/prev_whats_new/github_stats_3.8.4.rst
similarity index 98%
rename from doc/users/prev_whats_new/github_stats_3.8.4.rst
rename to doc/release/prev_whats_new/github_stats_3.8.4.rst
index 324393b12f9d..9b38d03e8464 100644
--- a/doc/users/prev_whats_new/github_stats_3.8.4.rst
+++ b/doc/release/prev_whats_new/github_stats_3.8.4.rst
@@ -1,3 +1,5 @@
+.. redirect-from:: /users/prev_whats_new/github_stats_3.8.4
+
.. _github-stats-3-8-4:
GitHub statistics for 3.8.4 (Apr 03, 2024)
diff --git a/doc/users/prev_whats_new/github_stats_3.9.0.rst b/doc/release/prev_whats_new/github_stats_3.9.0.rst
similarity index 99%
rename from doc/users/prev_whats_new/github_stats_3.9.0.rst
rename to doc/release/prev_whats_new/github_stats_3.9.0.rst
index 5ddbdfd6f2bd..cd84acc8e288 100644
--- a/doc/users/prev_whats_new/github_stats_3.9.0.rst
+++ b/doc/release/prev_whats_new/github_stats_3.9.0.rst
@@ -1,3 +1,5 @@
+.. redirect-from:: /users/prev_whats_new/github_stats_3.9.0
+
.. _github-stats-3-9-0:
GitHub statistics for 3.9.0 (May 15, 2024)
diff --git a/doc/users/prev_whats_new/github_stats_3.9.1.rst b/doc/release/prev_whats_new/github_stats_3.9.1.rst
similarity index 99%
rename from doc/users/prev_whats_new/github_stats_3.9.1.rst
rename to doc/release/prev_whats_new/github_stats_3.9.1.rst
index 1bd7860546cb..58848b388f0c 100644
--- a/doc/users/prev_whats_new/github_stats_3.9.1.rst
+++ b/doc/release/prev_whats_new/github_stats_3.9.1.rst
@@ -1,3 +1,5 @@
+.. redirect-from:: /users/prev_whats_new/github_stats_3.9.1
+
.. _github-stats-3-9-1:
GitHub statistics for 3.9.1 (Jul 04, 2024)
diff --git a/doc/users/prev_whats_new/github_stats_3.9.2.rst b/doc/release/prev_whats_new/github_stats_3.9.2.rst
similarity index 98%
rename from doc/users/prev_whats_new/github_stats_3.9.2.rst
rename to doc/release/prev_whats_new/github_stats_3.9.2.rst
index 542e0d81ce32..eb11dd62c3f3 100644
--- a/doc/users/prev_whats_new/github_stats_3.9.2.rst
+++ b/doc/release/prev_whats_new/github_stats_3.9.2.rst
@@ -1,3 +1,5 @@
+.. redirect-from:: /users/prev_whats_new/github_stats_3.9.2
+
.. _github-stats-3-9-2:
GitHub statistics for 3.9.2 (Aug 12, 2024)
diff --git a/doc/users/prev_whats_new/github_stats_3.9.3.rst b/doc/release/prev_whats_new/github_stats_3.9.3.rst
similarity index 99%
rename from doc/users/prev_whats_new/github_stats_3.9.3.rst
rename to doc/release/prev_whats_new/github_stats_3.9.3.rst
index 06f0232c338c..f0b8a7b59c0d 100644
--- a/doc/users/prev_whats_new/github_stats_3.9.3.rst
+++ b/doc/release/prev_whats_new/github_stats_3.9.3.rst
@@ -1,3 +1,5 @@
+.. redirect-from:: /users/prev_whats_new/github_stats_3.9.3
+
.. _github-stats-3-9-3:
GitHub statistics for 3.9.3 (Nov 30, 2024)
diff --git a/doc/users/prev_whats_new/github_stats_3.9.4.rst b/doc/release/prev_whats_new/github_stats_3.9.4.rst
similarity index 94%
rename from doc/users/prev_whats_new/github_stats_3.9.4.rst
rename to doc/release/prev_whats_new/github_stats_3.9.4.rst
index a821d2fc1f57..afedb6eccc27 100644
--- a/doc/users/prev_whats_new/github_stats_3.9.4.rst
+++ b/doc/release/prev_whats_new/github_stats_3.9.4.rst
@@ -1,3 +1,5 @@
+.. redirect-from:: /users/prev_whats_new/github_stats_3.9.4
+
.. _github-stats-3-9-4:
GitHub statistics for 3.9.4 (Dec 13, 2024)
diff --git a/doc/users/prev_whats_new/whats_new_0.98.4.rst b/doc/release/prev_whats_new/whats_new_0.98.4.rst
similarity index 99%
rename from doc/users/prev_whats_new/whats_new_0.98.4.rst
rename to doc/release/prev_whats_new/whats_new_0.98.4.rst
index 88d376cf79bf..fb08b8355971 100644
--- a/doc/users/prev_whats_new/whats_new_0.98.4.rst
+++ b/doc/release/prev_whats_new/whats_new_0.98.4.rst
@@ -1,3 +1,5 @@
+.. redirect-from:: /users/prev_whats_new/whats_new_0.98.4
+
.. _whats-new-0-98-4:
What's new in Matplotlib 0.98.4
diff --git a/doc/users/prev_whats_new/whats_new_0.99.rst b/doc/release/prev_whats_new/whats_new_0.99.rst
similarity index 98%
rename from doc/users/prev_whats_new/whats_new_0.99.rst
rename to doc/release/prev_whats_new/whats_new_0.99.rst
index 47e4b18ae62d..94996a24c50b 100644
--- a/doc/users/prev_whats_new/whats_new_0.99.rst
+++ b/doc/release/prev_whats_new/whats_new_0.99.rst
@@ -1,3 +1,5 @@
+.. redirect-from:: /users/prev_whats_new/whats_new_0.99
+
.. _whats-new-0-99:
What's new in Matplotlib 0.99 (Aug 29, 2009)
diff --git a/doc/users/prev_whats_new/whats_new_1.0.rst b/doc/release/prev_whats_new/whats_new_1.0.rst
similarity index 99%
rename from doc/users/prev_whats_new/whats_new_1.0.rst
rename to doc/release/prev_whats_new/whats_new_1.0.rst
index 772f241f4b23..99d40b3923b6 100644
--- a/doc/users/prev_whats_new/whats_new_1.0.rst
+++ b/doc/release/prev_whats_new/whats_new_1.0.rst
@@ -1,3 +1,5 @@
+.. redirect-from:: /users/prev_whats_new/whats_new_1.0
+
.. _whats-new-1-0:
What's new in Matplotlib 1.0 (Jul 06, 2010)
diff --git a/doc/users/prev_whats_new/whats_new_1.1.rst b/doc/release/prev_whats_new/whats_new_1.1.rst
similarity index 99%
rename from doc/users/prev_whats_new/whats_new_1.1.rst
rename to doc/release/prev_whats_new/whats_new_1.1.rst
index 3f48fc9f87b6..1e036fbae095 100644
--- a/doc/users/prev_whats_new/whats_new_1.1.rst
+++ b/doc/release/prev_whats_new/whats_new_1.1.rst
@@ -1,3 +1,5 @@
+.. redirect-from:: /users/prev_whats_new/whats_new_1.1
+
.. _whats-new-1-1:
What's new in Matplotlib 1.1 (Nov 02, 2011)
diff --git a/doc/users/prev_whats_new/whats_new_1.2.2.rst b/doc/release/prev_whats_new/whats_new_1.2.2.rst
similarity index 90%
rename from doc/users/prev_whats_new/whats_new_1.2.2.rst
rename to doc/release/prev_whats_new/whats_new_1.2.2.rst
index ab81018925cd..15e076e6cfaa 100644
--- a/doc/users/prev_whats_new/whats_new_1.2.2.rst
+++ b/doc/release/prev_whats_new/whats_new_1.2.2.rst
@@ -1,3 +1,5 @@
+.. redirect-from:: /users/prev_whats_new/whats_new_1.2.2
+
.. _whats-new-1-2-2:
What's new in Matplotlib 1.2.2
diff --git a/doc/users/prev_whats_new/whats_new_1.2.rst b/doc/release/prev_whats_new/whats_new_1.2.rst
similarity index 99%
rename from doc/users/prev_whats_new/whats_new_1.2.rst
rename to doc/release/prev_whats_new/whats_new_1.2.rst
index 43c729999d5b..7e25f60d632b 100644
--- a/doc/users/prev_whats_new/whats_new_1.2.rst
+++ b/doc/release/prev_whats_new/whats_new_1.2.rst
@@ -1,3 +1,5 @@
+.. redirect-from:: /users/prev_whats_new/whats_new_1.2
+
.. _whats-new-1-2:
diff --git a/doc/users/prev_whats_new/whats_new_1.3.rst b/doc/release/prev_whats_new/whats_new_1.3.rst
similarity index 99%
rename from doc/users/prev_whats_new/whats_new_1.3.rst
rename to doc/release/prev_whats_new/whats_new_1.3.rst
index af40f37f92b7..f5c7165538aa 100644
--- a/doc/users/prev_whats_new/whats_new_1.3.rst
+++ b/doc/release/prev_whats_new/whats_new_1.3.rst
@@ -1,3 +1,5 @@
+.. redirect-from:: /users/prev_whats_new/whats_new_1.3
+
.. _whats-new-1-3:
What's new in Matplotlib 1.3 (Aug 01, 2013)
diff --git a/doc/users/prev_whats_new/whats_new_1.4.rst b/doc/release/prev_whats_new/whats_new_1.4.rst
similarity index 99%
rename from doc/users/prev_whats_new/whats_new_1.4.rst
rename to doc/release/prev_whats_new/whats_new_1.4.rst
index eb0e93fd8883..994e12ec977b 100644
--- a/doc/users/prev_whats_new/whats_new_1.4.rst
+++ b/doc/release/prev_whats_new/whats_new_1.4.rst
@@ -1,3 +1,5 @@
+.. redirect-from:: /users/prev_whats_new/whats_new_1.4
+
.. _whats-new-1-4:
diff --git a/doc/users/prev_whats_new/whats_new_1.5.rst b/doc/release/prev_whats_new/whats_new_1.5.rst
similarity index 99%
rename from doc/users/prev_whats_new/whats_new_1.5.rst
rename to doc/release/prev_whats_new/whats_new_1.5.rst
index 5bb4d4b9b5e9..8de98aedb01d 100644
--- a/doc/users/prev_whats_new/whats_new_1.5.rst
+++ b/doc/release/prev_whats_new/whats_new_1.5.rst
@@ -1,3 +1,5 @@
+.. redirect-from:: /users/prev_whats_new/whats_new_1.5
+
.. _whats-new-1-5:
What's new in Matplotlib 1.5 (Oct 29, 2015)
diff --git a/doc/users/prev_whats_new/whats_new_2.0.0.rst b/doc/release/prev_whats_new/whats_new_2.0.0.rst
similarity index 99%
rename from doc/users/prev_whats_new/whats_new_2.0.0.rst
rename to doc/release/prev_whats_new/whats_new_2.0.0.rst
index 0f5edb7c0e3f..e6eea1984707 100644
--- a/doc/users/prev_whats_new/whats_new_2.0.0.rst
+++ b/doc/release/prev_whats_new/whats_new_2.0.0.rst
@@ -1,3 +1,5 @@
+.. redirect-from:: /users/prev_whats_new/whats_new_2.0.0
+
.. _whats-new-2-0-0:
What's new in Matplotlib 2.0 (Jan 17, 2017)
diff --git a/doc/users/prev_whats_new/whats_new_2.1.0.rst b/doc/release/prev_whats_new/whats_new_2.1.0.rst
similarity index 99%
rename from doc/users/prev_whats_new/whats_new_2.1.0.rst
rename to doc/release/prev_whats_new/whats_new_2.1.0.rst
index a66e2e10f3a2..426ce377b7d1 100644
--- a/doc/users/prev_whats_new/whats_new_2.1.0.rst
+++ b/doc/release/prev_whats_new/whats_new_2.1.0.rst
@@ -1,3 +1,5 @@
+.. redirect-from:: /users/prev_whats_new/whats_new_2.1.0
+
.. _whats-new-2-1-0:
What's new in Matplotlib 2.1.0 (Oct 7, 2017)
diff --git a/doc/users/prev_whats_new/whats_new_2.2.rst b/doc/release/prev_whats_new/whats_new_2.2.rst
similarity index 99%
rename from doc/users/prev_whats_new/whats_new_2.2.rst
rename to doc/release/prev_whats_new/whats_new_2.2.rst
index 6354a390860a..066b64d19cca 100644
--- a/doc/users/prev_whats_new/whats_new_2.2.rst
+++ b/doc/release/prev_whats_new/whats_new_2.2.rst
@@ -1,3 +1,5 @@
+.. redirect-from:: /users/prev_whats_new/whats_new_2.2
+
.. _whats-new-2-2-0:
What's new in Matplotlib 2.2 (Mar 06, 2018)
diff --git a/doc/users/prev_whats_new/whats_new_3.0.rst b/doc/release/prev_whats_new/whats_new_3.0.rst
similarity index 99%
rename from doc/users/prev_whats_new/whats_new_3.0.rst
rename to doc/release/prev_whats_new/whats_new_3.0.rst
index e3dd12c71a8e..207c9a5eacd5 100644
--- a/doc/users/prev_whats_new/whats_new_3.0.rst
+++ b/doc/release/prev_whats_new/whats_new_3.0.rst
@@ -1,3 +1,5 @@
+.. redirect-from:: /users/prev_whats_new/whats_new_3.0
+
.. _whats-new-3-0-0:
What's new in Matplotlib 3.0 (Sep 18, 2018)
diff --git a/doc/users/prev_whats_new/whats_new_3.1.0.rst b/doc/release/prev_whats_new/whats_new_3.1.0.rst
similarity index 99%
rename from doc/users/prev_whats_new/whats_new_3.1.0.rst
rename to doc/release/prev_whats_new/whats_new_3.1.0.rst
index 9f53435b89f6..689b035209bc 100644
--- a/doc/users/prev_whats_new/whats_new_3.1.0.rst
+++ b/doc/release/prev_whats_new/whats_new_3.1.0.rst
@@ -1,3 +1,5 @@
+.. redirect-from:: /users/prev_whats_new/whats_new_3.1.0
+
.. _whats-new-3-1-0:
What's new in Matplotlib 3.1 (May 18, 2019)
diff --git a/doc/users/prev_whats_new/whats_new_3.10.0.rst b/doc/release/prev_whats_new/whats_new_3.10.0.rst
similarity index 98%
rename from doc/users/prev_whats_new/whats_new_3.10.0.rst
rename to doc/release/prev_whats_new/whats_new_3.10.0.rst
index 06282cedad9a..82e67368805d 100644
--- a/doc/users/prev_whats_new/whats_new_3.10.0.rst
+++ b/doc/release/prev_whats_new/whats_new_3.10.0.rst
@@ -1,3 +1,5 @@
+.. redirect-from:: /users/prev_whats_new/whats_new_3.10.0
+
===================================================
What's new in Matplotlib 3.10.0 (December 13, 2024)
===================================================
@@ -329,7 +331,9 @@ In the following example the norm and cmap are changed on multiple plots simulta
colorizer.vmax = 2
colorizer.cmap = 'RdBu'
-All plotting methods that use a data-to-color pipeline now create a colorizer object if one is not provided. This can be re-used by subsequent artists such that they will share a single data-to-color pipeline:
+All plotting methods that use a data-to-color pipeline now create a colorizer object if
+one is not provided. This can be reused by subsequent artists such that they will share
+a single data-to-color pipeline:
.. plot::
:include-source: true
diff --git a/doc/users/prev_whats_new/whats_new_3.2.0.rst b/doc/release/prev_whats_new/whats_new_3.2.0.rst
similarity index 98%
rename from doc/users/prev_whats_new/whats_new_3.2.0.rst
rename to doc/release/prev_whats_new/whats_new_3.2.0.rst
index 12d7fab3af90..4fcba4e5a0fc 100644
--- a/doc/users/prev_whats_new/whats_new_3.2.0.rst
+++ b/doc/release/prev_whats_new/whats_new_3.2.0.rst
@@ -1,3 +1,5 @@
+.. redirect-from:: /users/prev_whats_new/whats_new_3.2.0
+
.. _whats-new-3-2-0:
What's new in Matplotlib 3.2 (Mar 04, 2020)
diff --git a/doc/users/prev_whats_new/whats_new_3.3.0.rst b/doc/release/prev_whats_new/whats_new_3.3.0.rst
similarity index 99%
rename from doc/users/prev_whats_new/whats_new_3.3.0.rst
rename to doc/release/prev_whats_new/whats_new_3.3.0.rst
index 94914bcc75db..86ee971792e4 100644
--- a/doc/users/prev_whats_new/whats_new_3.3.0.rst
+++ b/doc/release/prev_whats_new/whats_new_3.3.0.rst
@@ -1,3 +1,5 @@
+.. redirect-from:: /users/prev_whats_new/whats_new_3.3.0
+
.. _whats-new-3-3-0:
=============================================
diff --git a/doc/users/prev_whats_new/whats_new_3.4.0.rst b/doc/release/prev_whats_new/whats_new_3.4.0.rst
similarity index 99%
rename from doc/users/prev_whats_new/whats_new_3.4.0.rst
rename to doc/release/prev_whats_new/whats_new_3.4.0.rst
index 003cd85fa49d..3cddee85b56e 100644
--- a/doc/users/prev_whats_new/whats_new_3.4.0.rst
+++ b/doc/release/prev_whats_new/whats_new_3.4.0.rst
@@ -1,3 +1,5 @@
+.. redirect-from:: /users/prev_whats_new/whats_new_3.4.0
+
.. _whats-new-3-4-0:
=============================================
diff --git a/doc/users/prev_whats_new/whats_new_3.5.0.rst b/doc/release/prev_whats_new/whats_new_3.5.0.rst
similarity index 98%
rename from doc/users/prev_whats_new/whats_new_3.5.0.rst
rename to doc/release/prev_whats_new/whats_new_3.5.0.rst
index e67573702218..d43a390d2db9 100644
--- a/doc/users/prev_whats_new/whats_new_3.5.0.rst
+++ b/doc/release/prev_whats_new/whats_new_3.5.0.rst
@@ -1,3 +1,5 @@
+.. redirect-from:: /users/prev_whats_new/whats_new_3.5.0
+
=============================================
What's new in Matplotlib 3.5.0 (Nov 15, 2021)
=============================================
@@ -274,9 +276,9 @@ of the text inside the Axes of the `.TextBox` widget.
fig = plt.figure(figsize=(4, 3))
for i, alignment in enumerate(['left', 'center', 'right']):
- box_input = fig.add_axes([0.1, 0.7 - i*0.3, 0.8, 0.2])
- text_box = TextBox(ax=box_input, initial=f'{alignment} alignment',
- label='', textalignment=alignment)
+ box_input = fig.add_axes((0.1, 0.7 - i*0.3, 0.8, 0.2))
+ text_box = TextBox(ax=box_input, initial=f'{alignment} alignment',
+ label='', textalignment=alignment)
Simplifying the font setting for usetex mode
--------------------------------------------
@@ -375,9 +377,9 @@ attribute.
points = ax.scatter((3, 3), (1, 3), (1, 3), c='red', zorder=10,
label='zorder=10')
- ax.set_xlim((0, 5))
- ax.set_ylim((0, 5))
- ax.set_zlim((0, 2.5))
+ ax.set_xlim(0, 5)
+ ax.set_ylim(0, 5)
+ ax.set_zlim(0, 2.5)
plane = mpatches.Patch(facecolor='white', edgecolor='black',
label='zorder=1')
@@ -485,7 +487,7 @@ new styling parameters for the added handles.
ax = ax_old
valmin = 0
valinit = 0.5
- ax.set_xlim([0, 1])
+ ax.set_xlim(0, 1)
ax_old.axvspan(valmin, valinit, 0, 1)
ax.axvline(valinit, 0, 1, color="r", lw=1)
ax.set_xticks([])
diff --git a/doc/users/prev_whats_new/whats_new_3.5.2.rst b/doc/release/prev_whats_new/whats_new_3.5.2.rst
similarity index 90%
rename from doc/users/prev_whats_new/whats_new_3.5.2.rst
rename to doc/release/prev_whats_new/whats_new_3.5.2.rst
index 85b1c38862eb..98880653c9d8 100644
--- a/doc/users/prev_whats_new/whats_new_3.5.2.rst
+++ b/doc/release/prev_whats_new/whats_new_3.5.2.rst
@@ -1,3 +1,5 @@
+.. redirect-from:: /users/prev_whats_new/whats_new_3.5.2
+
=============================================
What's new in Matplotlib 3.5.2 (May 02, 2022)
=============================================
diff --git a/doc/users/prev_whats_new/whats_new_3.6.0.rst b/doc/release/prev_whats_new/whats_new_3.6.0.rst
similarity index 99%
rename from doc/users/prev_whats_new/whats_new_3.6.0.rst
rename to doc/release/prev_whats_new/whats_new_3.6.0.rst
index 9fcf8cebfc6f..57b162ca159d 100644
--- a/doc/users/prev_whats_new/whats_new_3.6.0.rst
+++ b/doc/release/prev_whats_new/whats_new_3.6.0.rst
@@ -1,3 +1,5 @@
+.. redirect-from:: /users/prev_whats_new/whats_new_3.6.0
+
=============================================
What's new in Matplotlib 3.6.0 (Sep 15, 2022)
=============================================
diff --git a/doc/users/prev_whats_new/whats_new_3.7.0.rst b/doc/release/prev_whats_new/whats_new_3.7.0.rst
similarity index 99%
rename from doc/users/prev_whats_new/whats_new_3.7.0.rst
rename to doc/release/prev_whats_new/whats_new_3.7.0.rst
index 1834cbf3726f..d2451bfa50bc 100644
--- a/doc/users/prev_whats_new/whats_new_3.7.0.rst
+++ b/doc/release/prev_whats_new/whats_new_3.7.0.rst
@@ -1,3 +1,5 @@
+.. redirect-from:: /users/prev_whats_new/whats_new_3.7.0
+
=============================================
What's new in Matplotlib 3.7.0 (Feb 13, 2023)
=============================================
diff --git a/doc/users/prev_whats_new/whats_new_3.8.0.rst b/doc/release/prev_whats_new/whats_new_3.8.0.rst
similarity index 99%
rename from doc/users/prev_whats_new/whats_new_3.8.0.rst
rename to doc/release/prev_whats_new/whats_new_3.8.0.rst
index 88f987172adb..2d5ffe3ad3e7 100644
--- a/doc/users/prev_whats_new/whats_new_3.8.0.rst
+++ b/doc/release/prev_whats_new/whats_new_3.8.0.rst
@@ -1,3 +1,5 @@
+.. redirect-from:: /users/prev_whats_new/whats_new_3.8.0
+
==============================================
What's new in Matplotlib 3.8.0 (Sept 13, 2023)
==============================================
@@ -359,7 +361,7 @@ The following delimiter names have been supported earlier, but can now be sized
* ``\leftparen`` and ``\rightparen``
There are really no obvious advantages in using these.
-Instead, they are are added for completeness.
+Instead, they are added for completeness.
``mathtext`` documentation improvements
---------------------------------------
@@ -513,7 +515,7 @@ Plot Directive now can make responsive images with "srcset"
The plot sphinx directive (``matplotlib.sphinxext.plot_directive``, invoked in
rst as ``.. plot::``) can be configured to automatically make higher res
-figures and add these to the the built html docs. In ``conf.py``::
+figures and add these to the built html docs. In ``conf.py``::
extensions = [
...
diff --git a/doc/users/prev_whats_new/whats_new_3.9.0.rst b/doc/release/prev_whats_new/whats_new_3.9.0.rst
similarity index 99%
rename from doc/users/prev_whats_new/whats_new_3.9.0.rst
rename to doc/release/prev_whats_new/whats_new_3.9.0.rst
index 85fabf86efbe..259bd2f35ee4 100644
--- a/doc/users/prev_whats_new/whats_new_3.9.0.rst
+++ b/doc/release/prev_whats_new/whats_new_3.9.0.rst
@@ -1,3 +1,5 @@
+.. redirect-from:: /users/prev_whats_new/whats_new_3.9.0
+
=============================================
What's new in Matplotlib 3.9.0 (May 15, 2024)
=============================================
diff --git a/doc/users/release_notes.rst b/doc/release/release_notes.rst
similarity index 97%
rename from doc/users/release_notes.rst
rename to doc/release/release_notes.rst
index ae06d9875988..f6499166536d 100644
--- a/doc/users/release_notes.rst
+++ b/doc/release/release_notes.rst
@@ -1,5 +1,6 @@
.. redirect-from:: /api/api_changes_old
.. redirect-from:: /users/whats_new_old
+.. redirect-from:: /users/release_notes
.. _release-notes:
@@ -21,6 +22,9 @@ 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.5.rst
+ prev_whats_new/github_stats_3.10.3.rst
+ prev_whats_new/github_stats_3.10.1.rst
prev_whats_new/github_stats_3.10.0.rst
Version 3.9
diff --git a/doc/users/release_notes_next.rst b/doc/release/release_notes_next.rst
similarity index 73%
rename from doc/users/release_notes_next.rst
rename to doc/release/release_notes_next.rst
index 6813f61c5f90..de10d5e8dc27 100644
--- a/doc/users/release_notes_next.rst
+++ b/doc/release/release_notes_next.rst
@@ -1,3 +1,5 @@
+.. redirect-from:: /users/release_notes_next
+
:orphan:
Next version
diff --git a/doc/sphinxext/redirect_from.py b/doc/sphinxext/redirect_from.py
index 37b56373a3bf..329352b3a3c8 100644
--- a/doc/sphinxext/redirect_from.py
+++ b/doc/sphinxext/redirect_from.py
@@ -94,10 +94,15 @@ def run(self):
domain = self.env.get_domain('redirect_from')
current_doc = self.env.path2doc(self.state.document.current_source)
redirected_reldoc, _ = self.env.relfn2path(redirected_doc, current_doc)
- if redirected_reldoc in domain.redirects:
+ if (
+ redirected_reldoc in domain.redirects
+ and domain.redirects[redirected_reldoc] != current_doc
+ ):
raise ValueError(
f"{redirected_reldoc} is already noted as redirecting to "
- f"{domain.redirects[redirected_reldoc]}")
+ f"{domain.redirects[redirected_reldoc]}\n"
+ f"Cannot also redirect it to {current_doc}"
+ )
domain.redirects[redirected_reldoc] = current_doc
return []
diff --git a/doc/users/faq.rst b/doc/users/faq.rst
index b08bd75cee4e..d13625ec9907 100644
--- a/doc/users/faq.rst
+++ b/doc/users/faq.rst
@@ -281,8 +281,23 @@ locators as desired because the two axes are independent.
Generate images without having a window appear
----------------------------------------------
-Simply do not call `~matplotlib.pyplot.show`, and directly save the figure to
-the desired format::
+The recommended approach since matplotlib 3.1 is to explicitly create a Figure
+instance::
+
+ from matplotlib.figure import Figure
+ fig = Figure()
+ ax = fig.subplots()
+ ax.plot([1, 2, 3])
+ fig.savefig('myfig.png')
+
+This prevents any interaction with GUI frameworks and the window manager.
+
+It's alternatively still possible to use the pyplot interface. Instead of
+calling `matplotlib.pyplot.show`, call `matplotlib.pyplot.savefig`.
+
+Additionally, you must ensure to close the figure after saving it. Not
+closing the figure is a memory leak, because pyplot keeps references
+to all not-yet-shown figures::
import matplotlib.pyplot as plt
plt.plot([1, 2, 3])
@@ -352,7 +367,7 @@ provide the following information in your e-mail to the `mailing list
* Matplotlib provides debugging information through the `logging` library, and
a helper function to set the logging level: one can call ::
- plt.set_loglevel("info") # or "debug" for more info
+ plt.set_loglevel("INFO") # or "DEBUG" for more info
to obtain this debugging information.
diff --git a/doc/users/getting_started/index.rst b/doc/users/getting_started/index.rst
index ac896687979d..dfbbd615b5cd 100644
--- a/doc/users/getting_started/index.rst
+++ b/doc/users/getting_started/index.rst
@@ -4,26 +4,7 @@ Getting started
Installation quick-start
------------------------
-.. grid:: 1 1 2 2
-
- .. grid-item::
-
- Install using `pip `__:
-
- .. code-block:: bash
-
- pip install matplotlib
-
- .. grid-item::
-
- Install using `conda `__:
-
- .. code-block:: bash
-
- conda install -c conda-forge matplotlib
-
-Further details are available in the :doc:`Installation Guide `.
-
+.. include:: /install/quick_install.inc.rst
Draw a first plot
-----------------
diff --git a/galleries/examples/README.txt b/galleries/examples/README.txt
index 31d4beae578d..363494ac7e6b 100644
--- a/galleries/examples/README.txt
+++ b/galleries/examples/README.txt
@@ -13,7 +13,7 @@ and source code.
For longer tutorials, see our :ref:`tutorials page `.
You can also find :ref:`external resources ` and
-a :ref:`FAQ ` in our :ref:`user guide `.
+a :ref:`FAQ ` in our :ref:`user guide `.
.. admonition:: Tagging!
diff --git a/galleries/examples/animation/rain.py b/galleries/examples/animation/rain.py
index eacfaecc59e2..a87eace6fe07 100644
--- a/galleries/examples/animation/rain.py
+++ b/galleries/examples/animation/rain.py
@@ -22,7 +22,7 @@
# Create new Figure and an Axes which fills it.
fig = plt.figure(figsize=(7, 7))
-ax = fig.add_axes([0, 0, 1, 1], frameon=False)
+ax = fig.add_axes((0, 0, 1, 1), frameon=False)
ax.set_xlim(0, 1), ax.set_xticks([])
ax.set_ylim(0, 1), ax.set_yticks([])
diff --git a/galleries/examples/animation/simple_scatter.py b/galleries/examples/animation/simple_scatter.py
index 3f8c285810a3..5afef75f6074 100644
--- a/galleries/examples/animation/simple_scatter.py
+++ b/galleries/examples/animation/simple_scatter.py
@@ -11,7 +11,7 @@
import matplotlib.animation as animation
fig, ax = plt.subplots()
-ax.set_xlim([0, 10])
+ax.set_xlim(0, 10)
scat = ax.scatter(1, 0)
x = np.linspace(0, 10)
diff --git a/galleries/examples/axes_grid1/demo_colorbar_with_inset_locator.py b/galleries/examples/axes_grid1/demo_colorbar_with_inset_locator.py
index f62a0f58e3bc..352c8527910e 100644
--- a/galleries/examples/axes_grid1/demo_colorbar_with_inset_locator.py
+++ b/galleries/examples/axes_grid1/demo_colorbar_with_inset_locator.py
@@ -1,17 +1,17 @@
"""
.. _demo-colorbar-with-inset-locator:
-===========================================================
-Control the position and size of a colorbar with Inset Axes
-===========================================================
+=========================================================================
+Control the position and size of a colorbar with inset_locator.inset_axes
+=========================================================================
This example shows how to control the position, height, and width of colorbars
-using `~mpl_toolkits.axes_grid1.inset_locator.inset_axes`.
+using `.inset_locator.inset_axes`.
-Inset Axes placement is controlled as for legends: either by providing a *loc*
-option ("upper right", "best", ...), or by providing a locator with respect to
-the parent bbox. Parameters such as *bbox_to_anchor* and *borderpad* likewise
-work in the same way, and are also demonstrated here.
+`.inset_locator.inset_axes` placement is controlled as for legends: either
+by providing a *loc* option ("upper right", "best", ...), or by providing a
+locator with respect to the parent bbox. Parameters such as *bbox_to_anchor*
+and *borderpad* likewise work in the same way, and are also demonstrated here.
Users should consider using `.Axes.inset_axes` instead (see
:ref:`colorbar_placement`).
@@ -21,12 +21,12 @@
import matplotlib.pyplot as plt
-from mpl_toolkits.axes_grid1.inset_locator import inset_axes
+from mpl_toolkits.axes_grid1 import inset_locator
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=[6, 3])
im1 = ax1.imshow([[1, 2], [2, 3]])
-axins1 = inset_axes(
+axins1 = inset_locator.inset_axes(
ax1,
width="50%", # width: 50% of parent_bbox width
height="5%", # height: 5%
@@ -36,7 +36,7 @@
fig.colorbar(im1, cax=axins1, orientation="horizontal", ticks=[1, 2, 3])
im = ax2.imshow([[1, 2], [2, 3]])
-axins = inset_axes(
+axins = inset_locator.inset_axes(
ax2,
width="5%", # width: 5% of parent_bbox width
height="50%", # height: 50%
diff --git a/galleries/examples/axes_grid1/make_room_for_ylabel_using_axesgrid.py b/galleries/examples/axes_grid1/make_room_for_ylabel_using_axesgrid.py
index f130ef4a6de2..7e914ff76b6b 100644
--- a/galleries/examples/axes_grid1/make_room_for_ylabel_using_axesgrid.py
+++ b/galleries/examples/axes_grid1/make_room_for_ylabel_using_axesgrid.py
@@ -10,7 +10,7 @@
from mpl_toolkits.axes_grid1.axes_divider import make_axes_area_auto_adjustable
fig = plt.figure()
-ax = fig.add_axes([0, 0, 1, 1])
+ax = fig.add_axes((0, 0, 1, 1))
ax.set_yticks([0.5], labels=["very long label"])
@@ -19,8 +19,8 @@
# %%
fig = plt.figure()
-ax1 = fig.add_axes([0, 0, 1, 0.5])
-ax2 = fig.add_axes([0, 0.5, 1, 0.5])
+ax1 = fig.add_axes((0, 0, 1, 0.5))
+ax2 = fig.add_axes((0, 0.5, 1, 0.5))
ax1.set_yticks([0.5], labels=["very long label"])
ax1.set_ylabel("Y label")
@@ -33,7 +33,7 @@
# %%
fig = plt.figure()
-ax1 = fig.add_axes([0, 0, 1, 1])
+ax1 = fig.add_axes((0, 0, 1, 1))
divider = make_axes_locatable(ax1)
ax2 = divider.append_axes("right", "100%", pad=0.3, sharey=ax1)
diff --git a/galleries/examples/axisartist/demo_axis_direction.py b/galleries/examples/axisartist/demo_axis_direction.py
index 9540599c6a7b..6bc46fe273a0 100644
--- a/galleries/examples/axisartist/demo_axis_direction.py
+++ b/galleries/examples/axisartist/demo_axis_direction.py
@@ -10,20 +10,15 @@
from matplotlib.projections import PolarAxes
from matplotlib.transforms import Affine2D
import mpl_toolkits.axisartist as axisartist
-import mpl_toolkits.axisartist.angle_helper as angle_helper
-import mpl_toolkits.axisartist.grid_finder as grid_finder
-from mpl_toolkits.axisartist.grid_helper_curvelinear import \
- GridHelperCurveLinear
+from mpl_toolkits.axisartist import angle_helper, grid_finder
+from mpl_toolkits.axisartist.grid_helper_curvelinear import GridHelperCurveLinear
def setup_axes(fig, rect):
"""Polar projection, but in a rectangular box."""
# see demo_curvelinear_grid.py for details
grid_helper = GridHelperCurveLinear(
- (
- Affine2D().scale(np.pi/180., 1.) +
- PolarAxes.PolarTransform()
- ),
+ Affine2D().scale(np.pi/180., 1.) + PolarAxes.PolarTransform(),
extreme_finder=angle_helper.ExtremeFinderCycle(
20, 20,
lon_cycle=360, lat_cycle=None,
diff --git a/galleries/examples/axisartist/demo_curvelinear_grid.py b/galleries/examples/axisartist/demo_curvelinear_grid.py
index fb1fbdd011ce..83fc1ce0ceaa 100644
--- a/galleries/examples/axisartist/demo_curvelinear_grid.py
+++ b/galleries/examples/axisartist/demo_curvelinear_grid.py
@@ -17,8 +17,7 @@
from matplotlib.projections import PolarAxes
from matplotlib.transforms import Affine2D
from mpl_toolkits.axisartist import Axes, HostAxes, angle_helper
-from mpl_toolkits.axisartist.grid_helper_curvelinear import \
- GridHelperCurveLinear
+from mpl_toolkits.axisartist.grid_helper_curvelinear import GridHelperCurveLinear
def curvelinear_test1(fig):
diff --git a/galleries/examples/axisartist/demo_curvelinear_grid2.py b/galleries/examples/axisartist/demo_curvelinear_grid2.py
index d4ac36cc717b..a3cd06ef6706 100644
--- a/galleries/examples/axisartist/demo_curvelinear_grid2.py
+++ b/galleries/examples/axisartist/demo_curvelinear_grid2.py
@@ -14,10 +14,8 @@
import numpy as np
from mpl_toolkits.axisartist.axislines import Axes
-from mpl_toolkits.axisartist.grid_finder import (ExtremeFinderSimple,
- MaxNLocator)
-from mpl_toolkits.axisartist.grid_helper_curvelinear import \
- GridHelperCurveLinear
+from mpl_toolkits.axisartist.grid_finder import ExtremeFinderSimple, MaxNLocator
+from mpl_toolkits.axisartist.grid_helper_curvelinear import GridHelperCurveLinear
def curvelinear_test1(fig):
diff --git a/galleries/examples/axisartist/demo_floating_axes.py b/galleries/examples/axisartist/demo_floating_axes.py
index add03e266d3e..e2218ae7a4c5 100644
--- a/galleries/examples/axisartist/demo_floating_axes.py
+++ b/galleries/examples/axisartist/demo_floating_axes.py
@@ -22,8 +22,7 @@
from matplotlib.transforms import Affine2D
import mpl_toolkits.axisartist.angle_helper as angle_helper
import mpl_toolkits.axisartist.floating_axes as floating_axes
-from mpl_toolkits.axisartist.grid_finder import (DictFormatter, FixedLocator,
- MaxNLocator)
+from mpl_toolkits.axisartist.grid_finder import DictFormatter, FixedLocator, MaxNLocator
# Fixing random state for reproducibility
np.random.seed(19680801)
diff --git a/galleries/examples/axisartist/demo_parasite_axes.py b/galleries/examples/axisartist/demo_parasite_axes.py
index 8565ef455c7e..800b9be32ac8 100644
--- a/galleries/examples/axisartist/demo_parasite_axes.py
+++ b/galleries/examples/axisartist/demo_parasite_axes.py
@@ -24,7 +24,7 @@
fig = plt.figure()
-host = fig.add_axes([0.15, 0.1, 0.65, 0.8], axes_class=HostAxes)
+host = fig.add_axes((0.15, 0.1, 0.65, 0.8), axes_class=HostAxes)
par1 = host.get_aux_axes(viewlim_mode=None, sharex=host)
par2 = host.get_aux_axes(viewlim_mode=None, sharex=host)
diff --git a/galleries/examples/axisartist/simple_axis_pad.py b/galleries/examples/axisartist/simple_axis_pad.py
index f40a1aa9f273..f436ae3ab79c 100644
--- a/galleries/examples/axisartist/simple_axis_pad.py
+++ b/galleries/examples/axisartist/simple_axis_pad.py
@@ -11,45 +11,29 @@
from matplotlib.projections import PolarAxes
from matplotlib.transforms import Affine2D
import mpl_toolkits.axisartist as axisartist
-import mpl_toolkits.axisartist.angle_helper as angle_helper
-import mpl_toolkits.axisartist.grid_finder as grid_finder
-from mpl_toolkits.axisartist.grid_helper_curvelinear import \
- GridHelperCurveLinear
+from mpl_toolkits.axisartist import angle_helper, grid_finder
+from mpl_toolkits.axisartist.grid_helper_curvelinear import GridHelperCurveLinear
def setup_axes(fig, rect):
"""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)
- grid_locator2 = grid_finder.MaxNLocator(5)
-
- tick_formatter1 = angle_helper.FormatterDMS()
-
- grid_helper = GridHelperCurveLinear(tr,
- extreme_finder=extreme_finder,
- grid_locator1=grid_locator1,
- grid_locator2=grid_locator2,
- tick_formatter1=tick_formatter1
- )
-
- ax1 = fig.add_subplot(
- rect, axes_class=axisartist.Axes, grid_helper=grid_helper)
- ax1.axis[:].set_visible(False)
- ax1.set_aspect(1.)
- ax1.set_xlim(-5, 12)
- ax1.set_ylim(-5, 10)
-
- return ax1
+ 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),
+ grid_locator2=grid_finder.MaxNLocator(5),
+ tick_formatter1=angle_helper.FormatterDMS(),
+ )
+ ax = fig.add_subplot(
+ rect, axes_class=axisartist.Axes, grid_helper=grid_helper,
+ aspect=1, xlim=(-5, 12), ylim=(-5, 10))
+ ax.axis[:].set_visible(False)
+ return ax
def add_floating_axis1(ax1):
@@ -74,9 +58,6 @@ def add_floating_axis2(ax1):
def ann(ax1, d):
- if plt.rcParams["text.usetex"]:
- d = d.replace("_", r"\_")
-
ax1.annotate(d, (0.5, 1), (5, -5),
xycoords="axes fraction", textcoords="offset points",
va="top", ha="center")
diff --git a/galleries/examples/event_handling/poly_editor.py b/galleries/examples/event_handling/poly_editor.py
index f6efd8bb8446..9cc2e5373ae5 100644
--- a/galleries/examples/event_handling/poly_editor.py
+++ b/galleries/examples/event_handling/poly_editor.py
@@ -203,6 +203,6 @@ def on_mouse_move(self, event):
p = PolygonInteractor(ax, poly)
ax.set_title('Click and drag a point to move it')
- ax.set_xlim((-2, 2))
- ax.set_ylim((-2, 2))
+ ax.set_xlim(-2, 2)
+ ax.set_ylim(-2, 2)
plt.show()
diff --git a/galleries/examples/event_handling/pong_sgskip.py b/galleries/examples/event_handling/pong_sgskip.py
index 583e51eacdc5..2c4c35a7cb35 100644
--- a/galleries/examples/event_handling/pong_sgskip.py
+++ b/galleries/examples/event_handling/pong_sgskip.py
@@ -134,9 +134,9 @@ def __init__(self, ax):
# create the initial line
self.ax = ax
ax.xaxis.set_visible(False)
- ax.set_xlim([0, 7])
+ ax.set_xlim(0, 7)
ax.yaxis.set_visible(False)
- ax.set_ylim([-1, 1])
+ ax.set_ylim(-1, 1)
pad_a_x = 0
pad_b_x = .50
pad_a_y = pad_b_y = .30
diff --git a/galleries/examples/images_contours_and_fields/barcode_demo.py b/galleries/examples/images_contours_and_fields/barcode_demo.py
index bdf48ca22531..5df58535650d 100644
--- a/galleries/examples/images_contours_and_fields/barcode_demo.py
+++ b/galleries/examples/images_contours_and_fields/barcode_demo.py
@@ -30,7 +30,7 @@
dpi = 100
fig = plt.figure(figsize=(len(code) * pixel_per_bar / dpi, 2), dpi=dpi)
-ax = fig.add_axes([0, 0, 1, 1]) # span the whole figure
+ax = fig.add_axes((0, 0, 1, 1)) # span the whole figure
ax.set_axis_off()
ax.imshow(code.reshape(1, -1), cmap='binary', aspect='auto',
interpolation='nearest')
diff --git a/galleries/examples/images_contours_and_fields/image_antialiasing.py b/galleries/examples/images_contours_and_fields/image_antialiasing.py
index 7f223f6998f2..10f563875767 100644
--- a/galleries/examples/images_contours_and_fields/image_antialiasing.py
+++ b/galleries/examples/images_contours_and_fields/image_antialiasing.py
@@ -245,7 +245,7 @@
# may serve a 100x100 version of the image, which will be downsampled.)
fig = plt.figure(figsize=(2, 2))
-ax = fig.add_axes([0, 0, 1, 1])
+ax = fig.add_axes((0, 0, 1, 1))
ax.imshow(aa[:400, :400], cmap='RdBu_r', interpolation='nearest')
plt.show()
# %%
diff --git a/galleries/examples/images_contours_and_fields/image_exact_placement.py b/galleries/examples/images_contours_and_fields/image_exact_placement.py
index a3b314a7c7c3..7c667dfed1af 100644
--- a/galleries/examples/images_contours_and_fields/image_exact_placement.py
+++ b/galleries/examples/images_contours_and_fields/image_exact_placement.py
@@ -134,13 +134,13 @@ def annotate_rect(ax):
fig = plt.figure(figsize=(fig_width / dpi, fig_height / dpi), facecolor='aliceblue')
# the position posA must be normalized by the figure width and height:
-ax = fig.add_axes([posA[0] / fig_width, posA[1] / fig_height,
- posA[2] / fig_width, posA[3] / fig_height])
+ax = fig.add_axes((posA[0] / fig_width, posA[1] / fig_height,
+ posA[2] / fig_width, posA[3] / fig_height))
ax.imshow(A, vmin=-1, vmax=1)
annotate_rect(ax)
-ax = fig.add_axes([posB[0] / fig_width, posB[1] / fig_height,
- posB[2] / fig_width, posB[3] / fig_height])
+ax = fig.add_axes((posB[0] / fig_width, posB[1] / fig_height,
+ posB[2] / fig_width, posB[3] / fig_height))
ax.imshow(B, vmin=-1, vmax=1)
plt.show()
# %%
diff --git a/galleries/examples/images_contours_and_fields/trigradient_demo.py b/galleries/examples/images_contours_and_fields/trigradient_demo.py
index aa3cbc889eba..dcfd23ada73b 100644
--- a/galleries/examples/images_contours_and_fields/trigradient_demo.py
+++ b/galleries/examples/images_contours_and_fields/trigradient_demo.py
@@ -9,8 +9,7 @@
import matplotlib.pyplot as plt
import numpy as np
-from matplotlib.tri import (CubicTriInterpolator, Triangulation,
- UniformTriRefiner)
+from matplotlib.tri import CubicTriInterpolator, Triangulation, UniformTriRefiner
# ----------------------------------------------------------------------------
diff --git a/galleries/examples/lines_bars_and_markers/broken_barh.py b/galleries/examples/lines_bars_and_markers/broken_barh.py
index 3714ca7c748d..a709e911773d 100644
--- a/galleries/examples/lines_bars_and_markers/broken_barh.py
+++ b/galleries/examples/lines_bars_and_markers/broken_barh.py
@@ -18,13 +18,13 @@
network = np.column_stack([10*np.random.random(10), np.full(10, 0.05)])
fig, ax = plt.subplots()
-# broken_barh(xranges, (ymin, height))
-ax.broken_barh(cpu_1, (-0.2, 0.4))
-ax.broken_barh(cpu_2, (0.8, 0.4))
-ax.broken_barh(cpu_3, (1.8, 0.4))
-ax.broken_barh(cpu_4, (2.8, 0.4))
-ax.broken_barh(disk, (3.8, 0.4), color="tab:orange")
-ax.broken_barh(network, (4.8, 0.4), color="tab:green")
+# broken_barh(xranges, (ypos, height))
+ax.broken_barh(cpu_1, (0, 0.4), align="center")
+ax.broken_barh(cpu_2, (1, 0.4), align="center")
+ax.broken_barh(cpu_3, (2, 0.4), align="center")
+ax.broken_barh(cpu_4, (3, 0.4), align="center")
+ax.broken_barh(disk, (4, 0.4), align="center", color="tab:orange")
+ax.broken_barh(network, (5, 0.4), align="center", color="tab:green")
ax.set_xlim(0, 10)
ax.set_yticks(range(6),
labels=["CPU 1", "CPU 2", "CPU 3", "CPU 4", "disk", "network"])
diff --git a/galleries/examples/lines_bars_and_markers/eventcollection_demo.py b/galleries/examples/lines_bars_and_markers/eventcollection_demo.py
index 1aa2fa622812..6854a13e0974 100644
--- a/galleries/examples/lines_bars_and_markers/eventcollection_demo.py
+++ b/galleries/examples/lines_bars_and_markers/eventcollection_demo.py
@@ -53,8 +53,8 @@
ax.add_collection(yevents2)
# set the limits
-ax.set_xlim([0, 1])
-ax.set_ylim([0, 1])
+ax.set_xlim(0, 1)
+ax.set_ylim(0, 1)
ax.set_title('line plot with data points')
diff --git a/galleries/examples/lines_bars_and_markers/hat_graph.py b/galleries/examples/lines_bars_and_markers/hat_graph.py
index 0fb611bc9262..25e5f0b1ead3 100644
--- a/galleries/examples/lines_bars_and_markers/hat_graph.py
+++ b/galleries/examples/lines_bars_and_markers/hat_graph.py
@@ -29,35 +29,28 @@ def hat_graph(ax, xlabels, values, group_labels):
The group labels displayed in the legend.
"""
- def label_bars(heights, rects):
- """Attach a text label on top of each bar."""
- for height, rect in zip(heights, rects):
- ax.annotate(f'{height}',
- xy=(rect.get_x() + rect.get_width() / 2, height),
- xytext=(0, 4), # 4 points vertical offset.
- textcoords='offset points',
- ha='center', va='bottom')
-
values = np.asarray(values)
- x = np.arange(values.shape[1])
- ax.set_xticks(x, labels=xlabels)
- spacing = 0.3 # spacing between hat groups
- width = (1 - spacing) / values.shape[0]
- heights0 = values[0]
- for i, (heights, group_label) in enumerate(zip(values, group_labels)):
- style = {'fill': False} if i == 0 else {'edgecolor': 'black'}
- rects = ax.bar(x - spacing/2 + i * width, heights - heights0,
- width, bottom=heights0, label=group_label, **style)
- label_bars(heights, rects)
+ color_cycle_colors = plt.rcParams['axes.prop_cycle'].by_key()['color']
+
+ # Draw the hats
+ bars = ax.grouped_bar(
+ (values - values[0]).T, bottom=values[0], tick_labels=xlabels,
+ labels=group_labels, edgecolor='black', group_spacing=0.8,
+ colors=['none'] + color_cycle_colors)
+ # Attach a text label on top of each bar
+ for bc, heights in zip(bars.bar_containers, values):
+ ax.bar_label(bc, heights, padding=4)
-# initialise labels and a numpy array make sure you have
+
+# Initialise labels and a numpy array make sure you have
# N labels of N number of values in the array
xlabels = ['I', 'II', 'III', 'IV', 'V']
playerA = np.array([5, 15, 22, 20, 25])
playerB = np.array([25, 32, 34, 30, 27])
-fig, ax = plt.subplots()
+fig, ax = plt.subplots(layout='constrained')
+
hat_graph(ax, xlabels, [playerA, playerB], ['Player A', 'Player B'])
# Add some text for labels, title and custom x-axis tick labels, etc.
@@ -67,7 +60,6 @@ def label_bars(heights, rects):
ax.set_title('Scores by number of game and players')
ax.legend()
-fig.tight_layout()
plt.show()
# %%
#
@@ -76,8 +68,8 @@ def label_bars(heights, rects):
# The use of the following functions, methods, classes and modules is shown
# in this example:
#
-# - `matplotlib.axes.Axes.bar` / `matplotlib.pyplot.bar`
-# - `matplotlib.axes.Axes.annotate` / `matplotlib.pyplot.annotate`
+# - `matplotlib.axes.Axes.grouped_bar` / `matplotlib.pyplot.grouped_bar`
+# - `matplotlib.axes.Axes.bar_label` / `matplotlib.pyplot.bar_label`
#
# .. tags::
#
diff --git a/galleries/examples/lines_bars_and_markers/markevery_demo.py b/galleries/examples/lines_bars_and_markers/markevery_demo.py
index 919e12cde952..da4da0ecf9f1 100644
--- a/galleries/examples/lines_bars_and_markers/markevery_demo.py
+++ b/galleries/examples/lines_bars_and_markers/markevery_demo.py
@@ -79,8 +79,8 @@
for ax, markevery in zip(axs.flat, cases):
ax.set_title(f'markevery={markevery}')
ax.plot(x, y, 'o', ls='-', ms=4, markevery=markevery)
- ax.set_xlim((6, 6.7))
- ax.set_ylim((1.1, 1.7))
+ ax.set_xlim(6, 6.7)
+ ax.set_ylim(1.1, 1.7)
# %%
# markevery on polar plots
diff --git a/galleries/examples/lines_bars_and_markers/multicolored_line.py b/galleries/examples/lines_bars_and_markers/multicolored_line.py
index 3a71225d0112..a643b2de160c 100644
--- a/galleries/examples/lines_bars_and_markers/multicolored_line.py
+++ b/galleries/examples/lines_bars_and_markers/multicolored_line.py
@@ -72,7 +72,6 @@ def colored_line(x, y, c, ax=None, **lc_kwargs):
# Plot the line collection to the axes
ax = ax or plt.gca()
ax.add_collection(lc)
- ax.autoscale_view()
return lc
diff --git a/galleries/examples/misc/anchored_artists.py b/galleries/examples/misc/anchored_artists.py
index bd1ec013c2a9..be600449bba6 100644
--- a/galleries/examples/misc/anchored_artists.py
+++ b/galleries/examples/misc/anchored_artists.py
@@ -17,8 +17,8 @@
from matplotlib import pyplot as plt
from matplotlib.lines import Line2D
-from matplotlib.offsetbox import (AnchoredOffsetbox, AuxTransformBox,
- DrawingArea, TextArea, VPacker)
+from matplotlib.offsetbox import (AnchoredOffsetbox, AuxTransformBox, DrawingArea,
+ TextArea, VPacker)
from matplotlib.patches import Circle, Ellipse
diff --git a/galleries/examples/misc/svg_filter_line.py b/galleries/examples/misc/svg_filter_line.py
index c6adec093bee..dd97dc975eda 100644
--- a/galleries/examples/misc/svg_filter_line.py
+++ b/galleries/examples/misc/svg_filter_line.py
@@ -17,7 +17,7 @@
import matplotlib.transforms as mtransforms
fig1 = plt.figure()
-ax = fig1.add_axes([0.1, 0.1, 0.8, 0.8])
+ax = fig1.add_axes((0.1, 0.1, 0.8, 0.8))
# draw lines
l1, = ax.plot([0.1, 0.5, 0.9], [0.1, 0.9, 0.5], "bo-",
diff --git a/galleries/examples/misc/svg_filter_pie.py b/galleries/examples/misc/svg_filter_pie.py
index b823cc9670c9..b19867be9a2f 100644
--- a/galleries/examples/misc/svg_filter_pie.py
+++ b/galleries/examples/misc/svg_filter_pie.py
@@ -19,7 +19,7 @@
# make a square figure and Axes
fig = plt.figure(figsize=(6, 6))
-ax = fig.add_axes([0.1, 0.1, 0.8, 0.8])
+ax = fig.add_axes((0.1, 0.1, 0.8, 0.8))
labels = 'Frogs', 'Hogs', 'Dogs', 'Logs'
fracs = [15, 30, 45, 10]
diff --git a/galleries/examples/mplot3d/box3d.py b/galleries/examples/mplot3d/box3d.py
index 807e3d496ec6..4d75c8bc2809 100644
--- a/galleries/examples/mplot3d/box3d.py
+++ b/galleries/examples/mplot3d/box3d.py
@@ -51,7 +51,7 @@
xmin, xmax = X.min(), X.max()
ymin, ymax = Y.min(), Y.max()
zmin, zmax = Z.min(), Z.max()
-ax.set(xlim=[xmin, xmax], ylim=[ymin, ymax], zlim=[zmin, zmax])
+ax.set(xlim=(xmin, xmax), ylim=(ymin, ymax), zlim=(zmin, zmax))
# Plot edges
edges_kw = dict(color='0.4', linewidth=1, zorder=1e3)
diff --git a/galleries/examples/pie_and_polar_charts/pie_features.py b/galleries/examples/pie_and_polar_charts/pie_features.py
index 47781a31a373..4c0eeaa4526e 100644
--- a/galleries/examples/pie_and_polar_charts/pie_features.py
+++ b/galleries/examples/pie_and_polar_charts/pie_features.py
@@ -108,7 +108,7 @@
fig, ax = plt.subplots()
ax.pie(sizes, labels=labels, autopct='%.0f%%',
- textprops={'size': 'smaller'}, radius=0.5)
+ textprops={'size': 'small'}, radius=0.5)
plt.show()
# %%
diff --git a/galleries/examples/pie_and_polar_charts/polar_demo.py b/galleries/examples/pie_and_polar_charts/polar_demo.py
index e4967079d19d..909fea094be5 100644
--- a/galleries/examples/pie_and_polar_charts/polar_demo.py
+++ b/galleries/examples/pie_and_polar_charts/polar_demo.py
@@ -4,6 +4,11 @@
==========
Demo of a line plot on a polar axis.
+
+The second plot shows the same data, but with the radial axis starting at r=1
+and the angular axis starting at 0 degrees and ending at 225 degrees. Setting
+the origin of the radial axis to 0 allows the radial ticks to be placed at the
+same location as the first plot.
"""
import matplotlib.pyplot as plt
import numpy as np
@@ -11,14 +16,29 @@
r = np.arange(0, 2, 0.01)
theta = 2 * np.pi * r
-fig, ax = plt.subplots(subplot_kw={'projection': 'polar'})
+fig, axs = plt.subplots(2, 1, figsize=(5, 8), subplot_kw={'projection': 'polar'},
+ layout='constrained')
+ax = axs[0]
ax.plot(theta, r)
ax.set_rmax(2)
-ax.set_rticks([0.5, 1, 1.5, 2]) # Less radial ticks
+ax.set_rticks([0.5, 1, 1.5, 2]) # Fewer radial ticks
ax.set_rlabel_position(-22.5) # Move radial labels away from plotted line
ax.grid(True)
ax.set_title("A line plot on a polar axis", va='bottom')
+
+ax = axs[1]
+ax.plot(theta, r)
+ax.set_rmax(2)
+ax.set_rmin(1) # Change the radial axis to only go from 1 to 2
+ax.set_rorigin(0) # Set the origin of the radial axis to 0
+ax.set_thetamin(0)
+ax.set_thetamax(225)
+ax.set_rticks([1, 1.5, 2]) # Fewer radial ticks
+ax.set_rlabel_position(-22.5) # Move radial labels away from plotted line
+
+ax.grid(True)
+ax.set_title("Same plot, but with reduced axis limits", va='bottom')
plt.show()
# %%
@@ -32,6 +52,8 @@
# - `matplotlib.projections.polar`
# - `matplotlib.projections.polar.PolarAxes`
# - `matplotlib.projections.polar.PolarAxes.set_rticks`
+# - `matplotlib.projections.polar.PolarAxes.set_rmin`
+# - `matplotlib.projections.polar.PolarAxes.set_rorigin`
# - `matplotlib.projections.polar.PolarAxes.set_rmax`
# - `matplotlib.projections.polar.PolarAxes.set_rlabel_position`
#
diff --git a/galleries/examples/scales/custom_scale.py b/galleries/examples/scales/custom_scale.py
index 0eedb16ec5cf..1b6bdd6f3e09 100644
--- a/galleries/examples/scales/custom_scale.py
+++ b/galleries/examples/scales/custom_scale.py
@@ -22,7 +22,7 @@
* You want to override the default locators and formatters for the axis
(``set_default_locators_and_formatters`` below).
- * You want to limit the range of the the axis (``limit_range_for_scale`` below).
+ * You want to limit the range of the axis (``limit_range_for_scale`` below).
"""
diff --git a/galleries/examples/shapes_and_collections/collections.py b/galleries/examples/shapes_and_collections/collections.py
index 1f60afda1c5f..a5b25fd8d2bb 100644
--- a/galleries/examples/shapes_and_collections/collections.py
+++ b/galleries/examples/shapes_and_collections/collections.py
@@ -1,7 +1,7 @@
"""
-=========================================================
-Line, Poly and RegularPoly Collection with autoscaling
-=========================================================
+=====================================
+Line, Poly and RegularPoly Collection
+=====================================
For the first two subplots, we will use spirals. Their size will be set in
plot units, not data units. Their positions will be set in data units by using
@@ -38,7 +38,7 @@
# Make some offsets
xyo = rs.randn(npts, 2)
-# Make a list of colors cycling through the default series.
+# Make a list of colors from the default color cycle.
colors = plt.rcParams['axes.prop_cycle'].by_key()['color']
fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(2, 2)
@@ -47,57 +47,36 @@
col = collections.LineCollection(
- [spiral], offsets=xyo, offset_transform=ax1.transData)
+ [spiral], offsets=xyo, offset_transform=ax1.transData, color=colors)
+# transform the line segments such that their size is given in points
trans = fig.dpi_scale_trans + transforms.Affine2D().scale(1.0/72.0)
col.set_transform(trans) # the points to pixels transform
-# Note: the first argument to the collection initializer
-# must be a list of sequences of (x, y) tuples; we have only
-# one sequence, but we still have to put it in a list.
-ax1.add_collection(col, autolim=True)
-# autolim=True enables autoscaling. For collections with
-# offsets like this, it is neither efficient nor accurate,
-# but it is good enough to generate a plot that you can use
-# as a starting point. If you know beforehand the range of
-# x and y that you want to show, it is better to set them
-# explicitly, leave out the *autolim* keyword argument (or set it to False),
-# and omit the 'ax1.autoscale_view()' call below.
-
-# Make a transform for the line segments such that their size is
-# given in points:
-col.set_color(colors)
-
-ax1.autoscale_view() # See comment above, after ax1.add_collection.
+ax1.add_collection(col)
ax1.set_title('LineCollection using offsets')
# The same data as above, but fill the curves.
col = collections.PolyCollection(
- [spiral], offsets=xyo, offset_transform=ax2.transData)
+ [spiral], offsets=xyo, offset_transform=ax2.transData, color=colors)
trans = transforms.Affine2D().scale(fig.dpi/72.0)
col.set_transform(trans) # the points to pixels transform
-ax2.add_collection(col, autolim=True)
-col.set_color(colors)
-
-
-ax2.autoscale_view()
+ax2.add_collection(col)
ax2.set_title('PolyCollection using offsets')
-# 7-sided regular polygons
+# 7-sided regular polygons
col = collections.RegularPolyCollection(
- 7, sizes=np.abs(xx) * 10.0, offsets=xyo, offset_transform=ax3.transData)
+ 7, sizes=np.abs(xx) * 10.0, offsets=xyo, offset_transform=ax3.transData,
+ color=colors)
trans = transforms.Affine2D().scale(fig.dpi / 72.0)
col.set_transform(trans) # the points to pixels transform
-ax3.add_collection(col, autolim=True)
-col.set_color(colors)
-ax3.autoscale_view()
+ax3.add_collection(col)
ax3.set_title('RegularPolyCollection using offsets')
# Simulate a series of ocean current profiles, successively
# offset by 0.1 m/s so that they form what is sometimes called
# a "waterfall" plot or a "stagger" plot.
-
nverts = 60
ncurves = 20
offs = (0.1, 0.0)
@@ -111,16 +90,12 @@
curve = np.column_stack([xxx, yy * 100])
segs.append(curve)
-col = collections.LineCollection(segs, offsets=offs)
-ax4.add_collection(col, autolim=True)
-col.set_color(colors)
-ax4.autoscale_view()
+col = collections.LineCollection(segs, offsets=offs, color=colors)
+ax4.add_collection(col)
ax4.set_title('Successive data offsets')
ax4.set_xlabel('Zonal velocity component (m/s)')
ax4.set_ylabel('Depth (m)')
-# Reverse the y-axis so depth increases downward
-ax4.set_ylim(ax4.get_ylim()[::-1])
-
+ax4.invert_yaxis() # so that depth increases downward
plt.show()
@@ -136,6 +111,5 @@
# - `matplotlib.collections.LineCollection`
# - `matplotlib.collections.RegularPolyCollection`
# - `matplotlib.axes.Axes.add_collection`
-# - `matplotlib.axes.Axes.autoscale_view`
# - `matplotlib.transforms.Affine2D`
# - `matplotlib.transforms.Affine2D.scale`
diff --git a/galleries/examples/shapes_and_collections/ellipse_collection.py b/galleries/examples/shapes_and_collections/ellipse_collection.py
index 7118e5f7abf2..39f0cb7dcb6a 100644
--- a/galleries/examples/shapes_and_collections/ellipse_collection.py
+++ b/galleries/examples/shapes_and_collections/ellipse_collection.py
@@ -30,7 +30,6 @@
offset_transform=ax.transData)
ec.set_array((X + Y).ravel())
ax.add_collection(ec)
-ax.autoscale_view()
ax.set_xlabel('X')
ax.set_ylabel('y')
cbar = plt.colorbar(ec)
@@ -47,5 +46,4 @@
# - `matplotlib.collections`
# - `matplotlib.collections.EllipseCollection`
# - `matplotlib.axes.Axes.add_collection`
-# - `matplotlib.axes.Axes.autoscale_view`
# - `matplotlib.cm.ScalarMappable.set_array`
diff --git a/galleries/examples/shapes_and_collections/hatch_demo.py b/galleries/examples/shapes_and_collections/hatch_demo.py
index f2ca490c4e37..8d44dba5489b 100644
--- a/galleries/examples/shapes_and_collections/hatch_demo.py
+++ b/galleries/examples/shapes_and_collections/hatch_demo.py
@@ -41,8 +41,8 @@
hatch='*', facecolor='y'))
axs['patches'].add_patch(Polygon([(10, 20), (30, 50), (50, 10)],
hatch='\\/...', facecolor='g'))
-axs['patches'].set_xlim([0, 40])
-axs['patches'].set_ylim([10, 60])
+axs['patches'].set_xlim(0, 40)
+axs['patches'].set_ylim(10, 60)
axs['patches'].set_aspect(1)
plt.show()
diff --git a/galleries/examples/showcase/anatomy.py b/galleries/examples/showcase/anatomy.py
index b1fbde9c8d7b..798e4204cad3 100644
--- a/galleries/examples/showcase/anatomy.py
+++ b/galleries/examples/showcase/anatomy.py
@@ -27,7 +27,7 @@
Y3 = np.random.uniform(Y1, Y2, len(X))
fig = plt.figure(figsize=(7.5, 7.5))
-ax = fig.add_axes([0.2, 0.17, 0.68, 0.7], aspect=1)
+ax = fig.add_axes((0.2, 0.17, 0.68, 0.7), aspect=1)
ax.xaxis.set_major_locator(MultipleLocator(1.000))
ax.xaxis.set_minor_locator(AutoMinorLocator(4))
diff --git a/galleries/examples/showcase/firefox.py b/galleries/examples/showcase/firefox.py
index 65682ccd7429..2026d253f6b6 100644
--- a/galleries/examples/showcase/firefox.py
+++ b/galleries/examples/showcase/firefox.py
@@ -48,7 +48,7 @@ def svg_parse(path):
xmax, ymax = verts.max(axis=0) + 1
fig = plt.figure(figsize=(5, 5), facecolor="0.75") # gray background
-ax = fig.add_axes([0, 0, 1, 1], frameon=False, aspect=1,
+ax = fig.add_axes((0, 0, 1, 1), frameon=False, aspect=1,
xlim=(xmin, xmax), # centering
ylim=(ymax, ymin), # centering, upside down
xticks=[], yticks=[]) # no ticks
diff --git a/galleries/examples/showcase/mandelbrot.py b/galleries/examples/showcase/mandelbrot.py
index ab40a061dc03..d8b7faf4c7b8 100644
--- a/galleries/examples/showcase/mandelbrot.py
+++ b/galleries/examples/showcase/mandelbrot.py
@@ -55,7 +55,7 @@ def mandelbrot_set(xmin, xmax, ymin, ymax, xn, yn, maxiter, horizon=2.0):
width = 10
height = 10*yn/xn
fig = plt.figure(figsize=(width, height), dpi=dpi)
- ax = fig.add_axes([0, 0, 1, 1], frameon=False, aspect=1)
+ ax = fig.add_axes((0, 0, 1, 1), frameon=False, aspect=1)
# Shaded rendering
light = colors.LightSource(azdeg=315, altdeg=10)
diff --git a/galleries/examples/showcase/xkcd.py b/galleries/examples/showcase/xkcd.py
index 3d6d5418a13f..9b4de0a90f5b 100644
--- a/galleries/examples/showcase/xkcd.py
+++ b/galleries/examples/showcase/xkcd.py
@@ -19,7 +19,7 @@
ax.spines[['top', 'right']].set_visible(False)
ax.set_xticks([])
ax.set_yticks([])
- ax.set_ylim([-30, 10])
+ ax.set_ylim(-30, 10)
data = np.ones(100)
data[70:] -= np.arange(30)
@@ -50,9 +50,9 @@
ax.xaxis.set_ticks_position('bottom')
ax.set_xticks([0, 1])
ax.set_xticklabels(['CONFIRMED BY\nEXPERIMENT', 'REFUTED BY\nEXPERIMENT'])
- ax.set_xlim([-0.5, 1.5])
+ ax.set_xlim(-0.5, 1.5)
ax.set_yticks([])
- ax.set_ylim([0, 110])
+ ax.set_ylim(0, 110)
ax.set_title("CLAIMS OF SUPERNATURAL POWERS")
diff --git a/galleries/examples/specialty_plots/leftventricle_bullseye.py b/galleries/examples/specialty_plots/leftventricle_bullseye.py
index 3ad02edbc630..285fcdaecc5e 100644
--- a/galleries/examples/specialty_plots/leftventricle_bullseye.py
+++ b/galleries/examples/specialty_plots/leftventricle_bullseye.py
@@ -55,7 +55,7 @@ def bullseye_plot(ax, data, seg_bold=None, cmap="viridis", norm=None):
r = np.linspace(0.2, 1, 4)
- ax.set(ylim=[0, 1], xticklabels=[], yticklabels=[])
+ ax.set(ylim=(0, 1), xticklabels=[], yticklabels=[])
ax.grid(False) # Remove grid
# Fill segments 1-6, 7-12, 13-16.
diff --git a/galleries/examples/specialty_plots/skewt.py b/galleries/examples/specialty_plots/skewt.py
index e25998a73c04..04d36c79f067 100644
--- a/galleries/examples/specialty_plots/skewt.py
+++ b/galleries/examples/specialty_plots/skewt.py
@@ -151,8 +151,7 @@ def upper_xlim(self):
import matplotlib.pyplot as plt
import numpy as np
- from matplotlib.ticker import (MultipleLocator, NullFormatter,
- ScalarFormatter)
+ from matplotlib.ticker import MultipleLocator, NullFormatter, ScalarFormatter
# Some example data.
data_txt = '''
diff --git a/galleries/examples/statistics/errorbar_limits.py b/galleries/examples/statistics/errorbar_limits.py
index f1d26460d947..fde18327af83 100644
--- a/galleries/examples/statistics/errorbar_limits.py
+++ b/galleries/examples/statistics/errorbar_limits.py
@@ -71,7 +71,7 @@
linestyle='none')
# tidy up the figure
-ax.set_xlim((0, 5.5))
+ax.set_xlim(0, 5.5)
ax.set_title('Errorbar upper and lower limits')
plt.show()
diff --git a/galleries/examples/subplots_axes_and_figures/axes_demo.py b/galleries/examples/subplots_axes_and_figures/axes_demo.py
index 07f3ca2070c2..16db465449a4 100644
--- a/galleries/examples/subplots_axes_and_figures/axes_demo.py
+++ b/galleries/examples/subplots_axes_and_figures/axes_demo.py
@@ -33,12 +33,12 @@
main_ax.set_title('Gaussian colored noise')
# this is an inset Axes over the main Axes
-right_inset_ax = fig.add_axes([.65, .6, .2, .2], facecolor='k')
+right_inset_ax = fig.add_axes((.65, .6, .2, .2), facecolor='k')
right_inset_ax.hist(s, 400, density=True)
right_inset_ax.set(title='Probability', xticks=[], yticks=[])
# this is another inset Axes over the main Axes
-left_inset_ax = fig.add_axes([.2, .6, .2, .2], facecolor='k')
+left_inset_ax = fig.add_axes((.2, .6, .2, .2), facecolor='k')
left_inset_ax.plot(t[:len(r)], r)
left_inset_ax.set(title='Impulse response', xlim=(0, .2), xticks=[], yticks=[])
diff --git a/galleries/examples/subplots_axes_and_figures/axes_zoom_effect.py b/galleries/examples/subplots_axes_and_figures/axes_zoom_effect.py
index c8d09de45888..93c7662576e1 100644
--- a/galleries/examples/subplots_axes_and_figures/axes_zoom_effect.py
+++ b/galleries/examples/subplots_axes_and_figures/axes_zoom_effect.py
@@ -7,10 +7,8 @@
import matplotlib.pyplot as plt
-from matplotlib.transforms import (Bbox, TransformedBbox,
- blended_transform_factory)
-from mpl_toolkits.axes_grid1.inset_locator import (BboxConnector,
- BboxConnectorPatch,
+from matplotlib.transforms import Bbox, TransformedBbox, blended_transform_factory
+from mpl_toolkits.axes_grid1.inset_locator import (BboxConnector, BboxConnectorPatch,
BboxPatch)
diff --git a/galleries/examples/subplots_axes_and_figures/geo_demo.py b/galleries/examples/subplots_axes_and_figures/geo_demo.py
index 256c440cc4d1..4c8d38cc8a52 100644
--- a/galleries/examples/subplots_axes_and_figures/geo_demo.py
+++ b/galleries/examples/subplots_axes_and_figures/geo_demo.py
@@ -6,7 +6,7 @@
This shows 4 possible geographic projections. Cartopy_ supports more
projections.
-.. _Cartopy: https://scitools.org.uk/cartopy/
+.. _Cartopy: https://cartopy.readthedocs.io
"""
import matplotlib.pyplot as plt
diff --git a/galleries/examples/subplots_axes_and_figures/gridspec_nested.py b/galleries/examples/subplots_axes_and_figures/gridspec_nested.py
index 025bdb1185a7..789cc0ae6b5b 100644
--- a/galleries/examples/subplots_axes_and_figures/gridspec_nested.py
+++ b/galleries/examples/subplots_axes_and_figures/gridspec_nested.py
@@ -1,4 +1,6 @@
"""
+.. redirect-from:: /gallery/userdemo/demo_gridspec06
+
================
Nested Gridspecs
================
diff --git a/galleries/examples/subplots_axes_and_figures/secondary_axis.py b/galleries/examples/subplots_axes_and_figures/secondary_axis.py
index 842b296f78cf..146de1cceeca 100644
--- a/galleries/examples/subplots_axes_and_figures/secondary_axis.py
+++ b/galleries/examples/subplots_axes_and_figures/secondary_axis.py
@@ -9,6 +9,9 @@
`.axes.Axes.secondary_yaxis`. This secondary axis can have a different scale
than the main axis by providing both a forward and an inverse conversion
function in a tuple to the *functions* keyword argument:
+
+See also :doc:`/gallery/subplots_axes_and_figures/two_scales` for the case
+where two scales are not related to one another, but independent.
"""
import datetime
diff --git a/galleries/examples/subplots_axes_and_figures/two_scales.py b/galleries/examples/subplots_axes_and_figures/two_scales.py
index 882fcac7866e..ea31f93c4251 100644
--- a/galleries/examples/subplots_axes_and_figures/two_scales.py
+++ b/galleries/examples/subplots_axes_and_figures/two_scales.py
@@ -12,7 +12,12 @@
Such Axes are generated by calling the `.Axes.twinx` method. Likewise,
`.Axes.twiny` is available to generate Axes that share a *y* axis but
have different top and bottom scales.
+
+See also :doc:`/gallery/subplots_axes_and_figures/secondary_axis` for the case
+where the two scales are not independent, but related (e.g., the same quantity
+in two different units).
"""
+
import matplotlib.pyplot as plt
import numpy as np
diff --git a/galleries/examples/text_labels_and_annotations/demo_annotation_box.py b/galleries/examples/text_labels_and_annotations/demo_annotation_box.py
index ad28c4abd96c..e6c21bd69107 100644
--- a/galleries/examples/text_labels_and_annotations/demo_annotation_box.py
+++ b/galleries/examples/text_labels_and_annotations/demo_annotation_box.py
@@ -13,8 +13,7 @@
import numpy as np
from matplotlib.cbook import get_sample_data
-from matplotlib.offsetbox import (AnnotationBbox, DrawingArea, OffsetImage,
- TextArea)
+from matplotlib.offsetbox import AnnotationBbox, DrawingArea, OffsetImage, TextArea
from matplotlib.patches import Circle
fig, ax = plt.subplots()
diff --git a/galleries/examples/text_labels_and_annotations/demo_text_path.py b/galleries/examples/text_labels_and_annotations/demo_text_path.py
index bb4bbc628caa..ae79ff937093 100644
--- a/galleries/examples/text_labels_and_annotations/demo_text_path.py
+++ b/galleries/examples/text_labels_and_annotations/demo_text_path.py
@@ -13,8 +13,7 @@
from matplotlib.cbook import get_sample_data
from matplotlib.image import BboxImage
-from matplotlib.offsetbox import (AnchoredOffsetbox, AnnotationBbox,
- AuxTransformBox)
+from matplotlib.offsetbox import AnchoredOffsetbox, AnnotationBbox, AuxTransformBox
from matplotlib.patches import PathPatch, Shadow
from matplotlib.text import TextPath
from matplotlib.transforms import IdentityTransform
diff --git a/galleries/examples/text_labels_and_annotations/mathtext_examples.py b/galleries/examples/text_labels_and_annotations/mathtext_examples.py
index f9f8e628e08b..cf395f0daf0e 100644
--- a/galleries/examples/text_labels_and_annotations/mathtext_examples.py
+++ b/galleries/examples/text_labels_and_annotations/mathtext_examples.py
@@ -61,7 +61,7 @@ def doall():
# Creating figure and axis.
fig = plt.figure(figsize=(7, 7))
- ax = fig.add_axes([0.01, 0.01, 0.98, 0.90],
+ ax = fig.add_axes((0.01, 0.01, 0.98, 0.90),
facecolor="white", frameon=True)
ax.set_xlim(0, 1)
ax.set_ylim(0, 1)
diff --git a/galleries/examples/ticks/date_demo_rrule.py b/galleries/examples/ticks/date_demo_rrule.py
index eb1fb605640d..948abde7584d 100644
--- a/galleries/examples/ticks/date_demo_rrule.py
+++ b/galleries/examples/ticks/date_demo_rrule.py
@@ -17,8 +17,7 @@
import matplotlib.pyplot as plt
import numpy as np
-from matplotlib.dates import (YEARLY, DateFormatter, RRuleLocator, drange,
- rrulewrapper)
+from matplotlib.dates import YEARLY, DateFormatter, RRuleLocator, drange, rrulewrapper
# Fixing random state for reproducibility
np.random.seed(19680801)
diff --git a/galleries/examples/ticks/date_formatters_locators.py b/galleries/examples/ticks/date_formatters_locators.py
index 39492168242f..8d4922931323 100644
--- a/galleries/examples/ticks/date_formatters_locators.py
+++ b/galleries/examples/ticks/date_formatters_locators.py
@@ -12,11 +12,11 @@
import matplotlib.pyplot as plt
import numpy as np
-from matplotlib.dates import (FR, MO, MONTHLY, SA, SU, TH, TU, WE,
- AutoDateFormatter, AutoDateLocator,
- ConciseDateFormatter, DateFormatter, DayLocator,
- HourLocator, MicrosecondLocator, MinuteLocator,
- MonthLocator, RRuleLocator, SecondLocator,
+# While these appear unused directly, they are used from eval'd strings.
+from matplotlib.dates import (FR, MO, MONTHLY, SA, SU, TH, TU, WE, AutoDateFormatter,
+ AutoDateLocator, ConciseDateFormatter, DateFormatter,
+ DayLocator, HourLocator, MicrosecondLocator,
+ MinuteLocator, MonthLocator, RRuleLocator, SecondLocator,
WeekdayLocator, YearLocator, rrulewrapper)
import matplotlib.ticker as ticker
diff --git a/galleries/examples/ticks/fig_axes_customize_simple.py b/galleries/examples/ticks/fig_axes_customize_simple.py
index 0dd85ec4bd93..07a569e3d31d 100644
--- a/galleries/examples/ticks/fig_axes_customize_simple.py
+++ b/galleries/examples/ticks/fig_axes_customize_simple.py
@@ -13,7 +13,7 @@
fig = plt.figure()
fig.patch.set_facecolor('lightgoldenrodyellow')
-ax1 = fig.add_axes([0.1, 0.3, 0.4, 0.4])
+ax1 = fig.add_axes((0.1, 0.3, 0.4, 0.4))
ax1.patch.set_facecolor('lightslategray')
ax1.tick_params(axis='x', labelcolor='tab:red', labelrotation=45, labelsize=16)
diff --git a/galleries/examples/user_interfaces/embedding_in_gtk3_panzoom_sgskip.py b/galleries/examples/user_interfaces/embedding_in_gtk3_panzoom_sgskip.py
index 7c3b04041009..c5e3279b031d 100644
--- a/galleries/examples/user_interfaces/embedding_in_gtk3_panzoom_sgskip.py
+++ b/galleries/examples/user_interfaces/embedding_in_gtk3_panzoom_sgskip.py
@@ -13,10 +13,8 @@
import numpy as np
-from matplotlib.backends.backend_gtk3 import \
- NavigationToolbar2GTK3 as NavigationToolbar
-from matplotlib.backends.backend_gtk3agg import \
- FigureCanvasGTK3Agg as FigureCanvas
+from matplotlib.backends.backend_gtk3 import NavigationToolbar2GTK3 as NavigationToolbar
+from matplotlib.backends.backend_gtk3agg import FigureCanvasGTK3Agg as FigureCanvas
from matplotlib.figure import Figure
win = Gtk.Window()
diff --git a/galleries/examples/user_interfaces/embedding_in_gtk3_sgskip.py b/galleries/examples/user_interfaces/embedding_in_gtk3_sgskip.py
index 51ceebb501e3..3ddff529b298 100644
--- a/galleries/examples/user_interfaces/embedding_in_gtk3_sgskip.py
+++ b/galleries/examples/user_interfaces/embedding_in_gtk3_sgskip.py
@@ -14,8 +14,7 @@
import numpy as np
-from matplotlib.backends.backend_gtk3agg import \
- FigureCanvasGTK3Agg as FigureCanvas
+from matplotlib.backends.backend_gtk3agg import FigureCanvasGTK3Agg as FigureCanvas
from matplotlib.figure import Figure
win = Gtk.Window()
diff --git a/galleries/examples/user_interfaces/embedding_in_gtk4_panzoom_sgskip.py b/galleries/examples/user_interfaces/embedding_in_gtk4_panzoom_sgskip.py
index e42e59459198..4dec7a219d4e 100644
--- a/galleries/examples/user_interfaces/embedding_in_gtk4_panzoom_sgskip.py
+++ b/galleries/examples/user_interfaces/embedding_in_gtk4_panzoom_sgskip.py
@@ -13,10 +13,8 @@
import numpy as np
-from matplotlib.backends.backend_gtk4 import \
- NavigationToolbar2GTK4 as NavigationToolbar
-from matplotlib.backends.backend_gtk4agg import \
- FigureCanvasGTK4Agg as FigureCanvas
+from matplotlib.backends.backend_gtk4 import NavigationToolbar2GTK4 as NavigationToolbar
+from matplotlib.backends.backend_gtk4agg import FigureCanvasGTK4Agg as FigureCanvas
from matplotlib.figure import Figure
@@ -44,10 +42,9 @@ def on_activate(app):
toolbar = NavigationToolbar(canvas)
vbox.append(toolbar)
- win.show()
+ win.present()
-app = Gtk.Application(
- application_id='org.matplotlib.examples.EmbeddingInGTK4PanZoom')
+app = Gtk.Application(application_id='org.matplotlib.examples.EmbeddingInGTK4PanZoom')
app.connect('activate', on_activate)
app.run(None)
diff --git a/galleries/examples/user_interfaces/embedding_in_gtk4_sgskip.py b/galleries/examples/user_interfaces/embedding_in_gtk4_sgskip.py
index 197cd7971088..bc90700e48d3 100644
--- a/galleries/examples/user_interfaces/embedding_in_gtk4_sgskip.py
+++ b/galleries/examples/user_interfaces/embedding_in_gtk4_sgskip.py
@@ -14,8 +14,7 @@
import numpy as np
-from matplotlib.backends.backend_gtk4agg import \
- FigureCanvasGTK4Agg as FigureCanvas
+from matplotlib.backends.backend_gtk4agg import FigureCanvasGTK4Agg as FigureCanvas
from matplotlib.figure import Figure
@@ -39,7 +38,7 @@ def on_activate(app):
canvas.set_size_request(800, 600)
sw.set_child(canvas)
- win.show()
+ win.present()
app = Gtk.Application(application_id='org.matplotlib.examples.EmbeddingInGTK4')
diff --git a/galleries/examples/user_interfaces/embedding_in_qt_sgskip.py b/galleries/examples/user_interfaces/embedding_in_qt_sgskip.py
index cea1a89c29df..35a22efd67ec 100644
--- a/galleries/examples/user_interfaces/embedding_in_qt_sgskip.py
+++ b/galleries/examples/user_interfaces/embedding_in_qt_sgskip.py
@@ -15,8 +15,7 @@
import numpy as np
from matplotlib.backends.backend_qtagg import FigureCanvas
-from matplotlib.backends.backend_qtagg import \
- NavigationToolbar2QT as NavigationToolbar
+from matplotlib.backends.backend_qtagg import NavigationToolbar2QT as NavigationToolbar
from matplotlib.backends.qt_compat import QtWidgets
from matplotlib.figure import Figure
diff --git a/galleries/examples/user_interfaces/embedding_in_tk_sgskip.py b/galleries/examples/user_interfaces/embedding_in_tk_sgskip.py
index 7474f40b4bac..2fa132a80227 100644
--- a/galleries/examples/user_interfaces/embedding_in_tk_sgskip.py
+++ b/galleries/examples/user_interfaces/embedding_in_tk_sgskip.py
@@ -11,8 +11,7 @@
# Implement the default Matplotlib key bindings.
from matplotlib.backend_bases import key_press_handler
-from matplotlib.backends.backend_tkagg import (FigureCanvasTkAgg,
- NavigationToolbar2Tk)
+from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
from matplotlib.figure import Figure
root = tkinter.Tk()
diff --git a/galleries/examples/user_interfaces/embedding_webagg_sgskip.py b/galleries/examples/user_interfaces/embedding_webagg_sgskip.py
index cdeb6419a18e..40d8a718facc 100644
--- a/galleries/examples/user_interfaces/embedding_webagg_sgskip.py
+++ b/galleries/examples/user_interfaces/embedding_webagg_sgskip.py
@@ -31,8 +31,8 @@
import numpy as np
import matplotlib as mpl
-from matplotlib.backends.backend_webagg import (
- FigureManagerWebAgg, new_figure_manager_given_figure)
+from matplotlib.backends.backend_webagg import (FigureManagerWebAgg,
+ new_figure_manager_given_figure)
from matplotlib.figure import Figure
diff --git a/galleries/examples/user_interfaces/fourier_demo_wx_sgskip.py b/galleries/examples/user_interfaces/fourier_demo_wx_sgskip.py
index f51917fda6b9..9e72b3745a40 100644
--- a/galleries/examples/user_interfaces/fourier_demo_wx_sgskip.py
+++ b/galleries/examples/user_interfaces/fourier_demo_wx_sgskip.py
@@ -194,10 +194,10 @@ def createPlots(self):
self.subplot1.set_xlabel("frequency f", fontsize=8)
self.subplot2.set_ylabel("Time Domain Waveform x(t)", fontsize=8)
self.subplot2.set_xlabel("time t", fontsize=8)
- self.subplot1.set_xlim([-6, 6])
- self.subplot1.set_ylim([0, 1])
- self.subplot2.set_xlim([-2, 2])
- self.subplot2.set_ylim([-2, 2])
+ self.subplot1.set_xlim(-6, 6)
+ self.subplot1.set_ylim(0, 1)
+ self.subplot2.set_xlim(-2, 2)
+ self.subplot2.set_ylim(-2, 2)
self.subplot1.text(0.05, .95,
r'$X(f) = \mathcal{F}\{x(t)\}$',
verticalalignment='top',
diff --git a/galleries/examples/user_interfaces/mpl_with_glade3_sgskip.py b/galleries/examples/user_interfaces/mpl_with_glade3_sgskip.py
index f39dbf4ca28e..8321405aa011 100644
--- a/galleries/examples/user_interfaces/mpl_with_glade3_sgskip.py
+++ b/galleries/examples/user_interfaces/mpl_with_glade3_sgskip.py
@@ -13,8 +13,7 @@
import numpy as np
-from matplotlib.backends.backend_gtk3agg import \
- FigureCanvasGTK3Agg as FigureCanvas
+from matplotlib.backends.backend_gtk3agg import FigureCanvasGTK3Agg as FigureCanvas
from matplotlib.figure import Figure
diff --git a/galleries/examples/userdemo/README.txt b/galleries/examples/userdemo/README.txt
deleted file mode 100644
index 7be351dc70dd..000000000000
--- a/galleries/examples/userdemo/README.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-.. _userdemo:
-
-Userdemo
-========
diff --git a/galleries/examples/userdemo/demo_gridspec06.py b/galleries/examples/userdemo/demo_gridspec06.py
deleted file mode 100644
index c42224ce1e7b..000000000000
--- a/galleries/examples/userdemo/demo_gridspec06.py
+++ /dev/null
@@ -1,38 +0,0 @@
-r"""
-================
-Nested GridSpecs
-================
-
-This example demonstrates the use of nested `.GridSpec`\s.
-"""
-
-import matplotlib.pyplot as plt
-import numpy as np
-
-
-def squiggle_xy(a, b, c, d):
- i = np.arange(0.0, 2*np.pi, 0.05)
- return np.sin(i*a)*np.cos(i*b), np.sin(i*c)*np.cos(i*d)
-
-
-fig = plt.figure(figsize=(8, 8))
-outer_grid = fig.add_gridspec(4, 4, wspace=0, hspace=0)
-
-for a in range(4):
- for b in range(4):
- # gridspec inside gridspec
- inner_grid = outer_grid[a, b].subgridspec(3, 3, wspace=0, hspace=0)
- axs = inner_grid.subplots() # Create all subplots for the inner grid.
- for (c, d), ax in np.ndenumerate(axs):
- ax.plot(*squiggle_xy(a + 1, b + 1, c + 1, d + 1))
- ax.set(xticks=[], yticks=[])
-
-# show only the outside spines
-for ax in fig.get_axes():
- ss = ax.get_subplotspec()
- ax.spines.top.set_visible(ss.is_first_row())
- ax.spines.bottom.set_visible(ss.is_last_row())
- ax.spines.left.set_visible(ss.is_first_col())
- ax.spines.right.set_visible(ss.is_last_col())
-
-plt.show()
diff --git a/galleries/examples/widgets/buttons.py b/galleries/examples/widgets/buttons.py
index 61249522c72c..2aef798399f4 100644
--- a/galleries/examples/widgets/buttons.py
+++ b/galleries/examples/widgets/buttons.py
@@ -41,8 +41,8 @@ def prev(self, event):
plt.draw()
callback = Index()
-axprev = fig.add_axes([0.7, 0.05, 0.1, 0.075])
-axnext = fig.add_axes([0.81, 0.05, 0.1, 0.075])
+axprev = fig.add_axes((0.7, 0.05, 0.1, 0.075))
+axnext = fig.add_axes((0.81, 0.05, 0.1, 0.075))
bnext = Button(axnext, 'Next')
bnext.on_clicked(callback.next)
bprev = Button(axprev, 'Previous')
diff --git a/galleries/examples/widgets/range_slider.py b/galleries/examples/widgets/range_slider.py
index f1bed7431e39..d2f2d1554246 100644
--- a/galleries/examples/widgets/range_slider.py
+++ b/galleries/examples/widgets/range_slider.py
@@ -34,7 +34,7 @@
axs[1].set_title('Histogram of pixel intensities')
# Create the RangeSlider
-slider_ax = fig.add_axes([0.20, 0.1, 0.60, 0.03])
+slider_ax = fig.add_axes((0.20, 0.1, 0.60, 0.03))
slider = RangeSlider(slider_ax, "Threshold", img.min(), img.max())
# Create the Vertical lines on the histogram
diff --git a/galleries/examples/widgets/slider_demo.py b/galleries/examples/widgets/slider_demo.py
index 7dc47b9c7b6f..e56390c182a0 100644
--- a/galleries/examples/widgets/slider_demo.py
+++ b/galleries/examples/widgets/slider_demo.py
@@ -38,7 +38,7 @@ def f(t, amplitude, frequency):
fig.subplots_adjust(left=0.25, bottom=0.25)
# Make a horizontal slider to control the frequency.
-axfreq = fig.add_axes([0.25, 0.1, 0.65, 0.03])
+axfreq = fig.add_axes((0.25, 0.1, 0.65, 0.03))
freq_slider = Slider(
ax=axfreq,
label='Frequency [Hz]',
@@ -48,7 +48,7 @@ def f(t, amplitude, frequency):
)
# Make a vertically oriented slider to control the amplitude
-axamp = fig.add_axes([0.1, 0.25, 0.0225, 0.63])
+axamp = fig.add_axes((0.1, 0.25, 0.0225, 0.63))
amp_slider = Slider(
ax=axamp,
label="Amplitude",
@@ -70,7 +70,7 @@ def update(val):
amp_slider.on_changed(update)
# Create a `matplotlib.widgets.Button` to reset the sliders to initial values.
-resetax = fig.add_axes([0.8, 0.025, 0.1, 0.04])
+resetax = fig.add_axes((0.8, 0.025, 0.1, 0.04))
button = Button(resetax, 'Reset', hovercolor='0.975')
diff --git a/galleries/examples/widgets/slider_snap_demo.py b/galleries/examples/widgets/slider_snap_demo.py
index 953ffaf63672..5826be32fa07 100644
--- a/galleries/examples/widgets/slider_snap_demo.py
+++ b/galleries/examples/widgets/slider_snap_demo.py
@@ -30,8 +30,8 @@
fig.subplots_adjust(bottom=0.25)
l, = ax.plot(t, s, lw=2)
-ax_freq = fig.add_axes([0.25, 0.1, 0.65, 0.03])
-ax_amp = fig.add_axes([0.25, 0.15, 0.65, 0.03])
+ax_freq = fig.add_axes((0.25, 0.1, 0.65, 0.03))
+ax_amp = fig.add_axes((0.25, 0.15, 0.65, 0.03))
# define the values to use for snapping
allowed_amplitudes = np.concatenate([np.linspace(.1, 5, 100), [6, 7, 8, 9]])
@@ -60,7 +60,7 @@ def update(val):
sfreq.on_changed(update)
samp.on_changed(update)
-ax_reset = fig.add_axes([0.8, 0.025, 0.1, 0.04])
+ax_reset = fig.add_axes((0.8, 0.025, 0.1, 0.04))
button = Button(ax_reset, 'Reset', hovercolor='0.975')
diff --git a/galleries/examples/widgets/textbox.py b/galleries/examples/widgets/textbox.py
index d5f02b82a30b..2121ce8594ce 100644
--- a/galleries/examples/widgets/textbox.py
+++ b/galleries/examples/widgets/textbox.py
@@ -39,7 +39,7 @@ def submit(expression):
plt.draw()
-axbox = fig.add_axes([0.1, 0.05, 0.8, 0.075])
+axbox = fig.add_axes((0.1, 0.05, 0.8, 0.075))
text_box = TextBox(axbox, "Evaluate", textalignment="center")
text_box.on_submit(submit)
text_box.set_val("t ** 2") # Trigger `submit` with the initial string.
diff --git a/galleries/tutorials/artists.py b/galleries/tutorials/artists.py
index a258eb71d447..4f93f7c71a6e 100644
--- a/galleries/tutorials/artists.py
+++ b/galleries/tutorials/artists.py
@@ -70,7 +70,7 @@ class in the Matplotlib API, and the one you will be working with most
coordinates::
fig2 = plt.figure()
- ax2 = fig2.add_axes([0.15, 0.1, 0.7, 0.3])
+ ax2 = fig2.add_axes((0.15, 0.1, 0.7, 0.3))
Continuing with our example::
@@ -134,7 +134,7 @@ class in the Matplotlib API, and the one you will be working with most
# Fixing random state for reproducibility
np.random.seed(19680801)
-ax2 = fig.add_axes([0.15, 0.1, 0.7, 0.3])
+ax2 = fig.add_axes((0.15, 0.1, 0.7, 0.3))
n, bins, patches = ax2.hist(np.random.randn(1000), 50,
facecolor='yellow', edgecolor='yellow')
ax2.set_xlabel('Time [s]')
@@ -295,7 +295,7 @@ class in the Matplotlib API, and the one you will be working with most
#
# In [157]: ax1 = fig.add_subplot(211)
#
-# In [158]: ax2 = fig.add_axes([0.1, 0.1, 0.7, 0.3])
+# In [158]: ax2 = fig.add_axes((0.1, 0.1, 0.7, 0.3))
#
# In [159]: ax1
# Out[159]:
@@ -669,7 +669,7 @@ class in the Matplotlib API, and the one you will be working with most
rect = fig.patch # a rectangle instance
rect.set_facecolor('lightgoldenrodyellow')
-ax1 = fig.add_axes([0.1, 0.3, 0.4, 0.4])
+ax1 = fig.add_axes((0.1, 0.3, 0.4, 0.4))
rect = ax1.patch
rect.set_facecolor('lightslategray')
diff --git a/galleries/tutorials/images.py b/galleries/tutorials/images.py
index 0867f7b6d672..a7c474dab40b 100644
--- a/galleries/tutorials/images.py
+++ b/galleries/tutorials/images.py
@@ -33,8 +33,8 @@
In [1]: %matplotlib inline
-This turns on inline plotting, where plot graphics will appear in your
-notebook. This has important implications for interactivity. For inline plotting, commands in
+This turns on inline plotting, where plot graphics will appear in your notebook. This
+has important implications for interactivity. For inline plotting, commands in
cells below the cell that outputs a plot will not affect the plot. For example,
changing the colormap is not possible from cells below the cell that creates a plot.
However, for other backends, such as Qt, that open a separate window,
diff --git a/galleries/tutorials/index.rst b/galleries/tutorials/index.rst
index ace37dcb6f57..48187a862a2e 100644
--- a/galleries/tutorials/index.rst
+++ b/galleries/tutorials/index.rst
@@ -7,7 +7,7 @@ This page contains a few tutorials for using Matplotlib. For the old tutorials,
For shorter examples, see our :ref:`examples page `.
You can also find :ref:`external resources ` and
-a :ref:`FAQ ` in our :ref:`user guide `.
+a :ref:`FAQ ` in our :ref:`user guide `.
.. raw:: html
diff --git a/galleries/tutorials/lifecycle.py b/galleries/tutorials/lifecycle.py
index 4aae4d6c1dbc..4c009f802cf4 100644
--- a/galleries/tutorials/lifecycle.py
+++ b/galleries/tutorials/lifecycle.py
@@ -169,7 +169,7 @@
ax.barh(group_names, group_data)
labels = ax.get_xticklabels()
plt.setp(labels, rotation=45, horizontalalignment='right')
-ax.set(xlim=[-10000, 140000], xlabel='Total Revenue', ylabel='Company',
+ax.set(xlim=(-10000, 140000), xlabel='Total Revenue', ylabel='Company',
title='Company Revenue')
# %%
@@ -187,7 +187,7 @@
ax.barh(group_names, group_data)
labels = ax.get_xticklabels()
plt.setp(labels, rotation=45, horizontalalignment='right')
-ax.set(xlim=[-10000, 140000], xlabel='Total Revenue', ylabel='Company',
+ax.set(xlim=(-10000, 140000), xlabel='Total Revenue', ylabel='Company',
title='Company Revenue')
# %%
@@ -220,7 +220,7 @@ def currency(x, pos):
labels = ax.get_xticklabels()
plt.setp(labels, rotation=45, horizontalalignment='right')
-ax.set(xlim=[-10000, 140000], xlabel='Total Revenue', ylabel='Company',
+ax.set(xlim=(-10000, 140000), xlabel='Total Revenue', ylabel='Company',
title='Company Revenue')
ax.xaxis.set_major_formatter(currency)
@@ -248,7 +248,7 @@ def currency(x, pos):
# Now we move our title up since it's getting a little cramped
ax.title.set(y=1.05)
-ax.set(xlim=[-10000, 140000], xlabel='Total Revenue', ylabel='Company',
+ax.set(xlim=(-10000, 140000), xlabel='Total Revenue', ylabel='Company',
title='Company Revenue')
ax.xaxis.set_major_formatter(currency)
ax.set_xticks([0, 25e3, 50e3, 75e3, 100e3, 125e3])
diff --git a/galleries/users_explain/animations/animations.py b/galleries/users_explain/animations/animations.py
index a0669956ab81..dca49fc5228e 100644
--- a/galleries/users_explain/animations/animations.py
+++ b/galleries/users_explain/animations/animations.py
@@ -111,7 +111,7 @@
scat = ax.scatter(t[0], z[0], c="b", s=5, label=f'v0 = {v0} m/s')
line2 = ax.plot(t[0], z2[0], label=f'v0 = {v02} m/s')[0]
-ax.set(xlim=[0, 3], ylim=[-4, 10], xlabel='Time [s]', ylabel='Z [m]')
+ax.set(xlim=(0, 3), ylim=(-4, 10), xlabel='Time [s]', ylabel='Z [m]')
ax.legend()
diff --git a/galleries/users_explain/artists/color_cycle.py b/galleries/users_explain/artists/color_cycle.py
index aa978e62b269..58170fad5b58 100644
--- a/galleries/users_explain/artists/color_cycle.py
+++ b/galleries/users_explain/artists/color_cycle.py
@@ -52,7 +52,7 @@
# %%
# Now we'll generate a figure with two Axes, one on top of the other. On the
-# first axis, we'll plot with the default cycler. On the second axis, we'll
+# first axes, we'll plot with the default cycler. On the second axes, we'll
# set the ``prop_cycle`` using :func:`matplotlib.axes.Axes.set_prop_cycle`,
# which will only set the ``prop_cycle`` for this :mod:`matplotlib.axes.Axes`
# instance. We'll use a second ``cycler`` that combines a color cycler and a
diff --git a/galleries/users_explain/artists/imshow_extent.py b/galleries/users_explain/artists/imshow_extent.py
index d16a15f1e9f9..a6daa3a541c1 100644
--- a/galleries/users_explain/artists/imshow_extent.py
+++ b/galleries/users_explain/artists/imshow_extent.py
@@ -3,8 +3,8 @@
.. _imshow_extent:
-*origin* and *extent* in `~.Axes.imshow`
-========================================
+Positioning and orientation of `~.Axes.imshow` images
+=====================================================
:meth:`~.Axes.imshow` allows you to render an image (either a 2D array which
will be color-mapped (based on *norm* and *cmap*) or a 3D RGB(A) array which
diff --git a/galleries/users_explain/artists/transforms_tutorial.py b/galleries/users_explain/artists/transforms_tutorial.py
index f8a3e98e8077..1a25f1f87c88 100644
--- a/galleries/users_explain/artists/transforms_tutorial.py
+++ b/galleries/users_explain/artists/transforms_tutorial.py
@@ -64,10 +64,9 @@
| |is top right of the output in | |
| |"display units". | |
| | | |
-| |The exact interpretation of the | |
-| |units depends on the back end. For | |
-| |example it is pixels for Agg and | |
-| |points for svg/pdf. | |
+| |"Display units" depends on the | |
+| |backend. For example, Agg uses | |
+| |pixels, and SVG/PDF use points. | |
+----------------+-----------------------------------+-----------------------------+
The `~matplotlib.transforms.Transform` objects are naive to the source and
@@ -401,7 +400,7 @@
fig, ax = plt.subplots()
xdata, ydata = (0.2, 0.7), (0.5, 0.5)
ax.plot(xdata, ydata, "o")
-ax.set_xlim((0, 1))
+ax.set_xlim(0, 1)
trans = (fig.dpi_scale_trans +
transforms.ScaledTranslation(xdata[0], ydata[0], ax.transData))
diff --git a/galleries/users_explain/axes/arranging_axes.py b/galleries/users_explain/axes/arranging_axes.py
index bc537e15c12c..64879d4a696d 100644
--- a/galleries/users_explain/axes/arranging_axes.py
+++ b/galleries/users_explain/axes/arranging_axes.py
@@ -103,8 +103,8 @@
w, h = 4, 3
margin = 0.5
fig = plt.figure(figsize=(w, h), facecolor='lightblue')
-ax = fig.add_axes([margin / w, margin / h, (w - 2 * margin) / w,
- (h - 2 * margin) / h])
+ax = fig.add_axes((margin / w, margin / h,
+ (w - 2 * margin) / w, (h - 2 * margin) / h))
# %%
diff --git a/galleries/users_explain/axes/autoscale.py b/galleries/users_explain/axes/autoscale.py
index df1fbbc8aea8..337960302c38 100644
--- a/galleries/users_explain/axes/autoscale.py
+++ b/galleries/users_explain/axes/autoscale.py
@@ -18,7 +18,6 @@
import matplotlib.pyplot as plt
import numpy as np
-import matplotlib as mpl
x = np.linspace(-2 * np.pi, 2 * np.pi, 100)
y = np.sinc(x)
@@ -159,22 +158,3 @@
ax.autoscale(enable=None, axis="x", tight=True)
print(ax.margins())
-
-# %%
-# Working with collections
-# ------------------------
-#
-# Autoscale works out of the box for all lines, patches, and images added to
-# the Axes. One of the artists that it won't work with is a `.Collection`.
-# After adding a collection to the Axes, one has to manually trigger the
-# `~matplotlib.axes.Axes.autoscale_view()` to recalculate
-# axes limits.
-
-fig, ax = plt.subplots()
-collection = mpl.collections.StarPolygonCollection(
- 5, rotation=0, sizes=(250,), # five point star, zero angle, size 250px
- offsets=np.column_stack([x, y]), # Set the positions
- offset_transform=ax.transData, # Propagate transformations of the Axes
-)
-ax.add_collection(collection)
-ax.autoscale_view()
diff --git a/galleries/users_explain/axes/axes_intro.rst b/galleries/users_explain/axes/axes_intro.rst
index 16738d929056..bb3094495026 100644
--- a/galleries/users_explain/axes/axes_intro.rst
+++ b/galleries/users_explain/axes/axes_intro.rst
@@ -52,8 +52,8 @@ Axes are added using methods on `~.Figure` objects, or via the `~.pyplot` interf
There are a number of other methods for adding Axes to a Figure:
-* `.Figure.add_axes`: manually position an Axes. ``fig.add_axes([0, 0, 1,
- 1])`` makes an Axes that fills the whole figure.
+* `.Figure.add_axes`: manually position an Axes. ``fig.add_axes((0, 0, 1, 1))`` makes an
+ Axes that fills the whole figure.
* `.pyplot.subplots` and `.Figure.subplots`: add a grid of Axes as in the example
above. The pyplot version returns both the Figure object and an array of
Axes. Note that ``fig, ax = plt.subplots()`` adds a single Axes to a Figure.
@@ -143,7 +143,7 @@ Other important methods set the extent on the axes (`~.axes.Axes.set_xlim`, `~.a
x = 2**np.cumsum(np.random.randn(200))
linesx = ax.plot(t, x)
ax.set_yscale('log')
- ax.set_xlim([20, 180])
+ ax.set_xlim(20, 180)
The Axes class also has helpers to deal with Axis ticks and their labels. Most straight-forward is `~.axes.Axes.set_xticks` and `~.axes.Axes.set_yticks` which manually set the tick locations and optionally their labels. Minor ticks can be toggled with `~.axes.Axes.minorticks_on` or `~.axes.Axes.minorticks_off`.
diff --git a/galleries/users_explain/axes/axes_scales.py b/galleries/users_explain/axes/axes_scales.py
index 6b163835070c..f901c012974a 100644
--- a/galleries/users_explain/axes/axes_scales.py
+++ b/galleries/users_explain/axes/axes_scales.py
@@ -171,7 +171,7 @@ def inverse(a):
ax.set_yscale('function', functions=(forward, inverse))
ax.set_title('function: Mercator')
ax.grid(True)
-ax.set_xlim([0, 180])
+ax.set_xlim(0, 180)
ax.yaxis.set_minor_formatter(NullFormatter())
ax.yaxis.set_major_locator(FixedLocator(np.arange(0, 90, 10)))
diff --git a/galleries/users_explain/axes/constrainedlayout_guide.py b/galleries/users_explain/axes/constrainedlayout_guide.py
index 4eeaed980843..5c2749804740 100644
--- a/galleries/users_explain/axes/constrainedlayout_guide.py
+++ b/galleries/users_explain/axes/constrainedlayout_guide.py
@@ -164,7 +164,7 @@ def example_plot(ax, fontsize=12, hide_labels=False):
# Legends
# =======
#
-# Legends can be placed outside of their parent axis.
+# Legends can be placed outside of their parent axes.
# *Constrained layout* is designed to handle this for :meth:`.Axes.legend`.
# However, *constrained layout* does *not* handle legends being created via
# :meth:`.Figure.legend` (yet).
diff --git a/galleries/users_explain/axes/legend_guide.py b/galleries/users_explain/axes/legend_guide.py
index 5da3ceafe387..ec0468fe172d 100644
--- a/galleries/users_explain/axes/legend_guide.py
+++ b/galleries/users_explain/axes/legend_guide.py
@@ -1,7 +1,7 @@
"""
.. redirect-from:: /tutorials/intermediate/legend_guide
-.. redirect-from:: /galleries/examples/userdemo/simple_legend01
-.. redirect-from:: /galleries/examples/userdemo/simple_legend02
+.. redirect-from:: /gallery/userdemo/simple_legend01
+.. redirect-from:: /gallery/userdemo/simple_legend02
.. _legend_guide:
diff --git a/galleries/users_explain/colors/colors.py b/galleries/users_explain/colors/colors.py
index c91a5fcb0dbe..97a281bf1977 100644
--- a/galleries/users_explain/colors/colors.py
+++ b/galleries/users_explain/colors/colors.py
@@ -197,7 +197,7 @@ def demo(sty):
if f'xkcd:{name}' in mcolors.XKCD_COLORS}
fig = plt.figure(figsize=[9, 5])
-ax = fig.add_axes([0, 0, 1, 1])
+ax = fig.add_axes((0, 0, 1, 1))
n_groups = 3
n_rows = len(overlap) // n_groups + 1
diff --git a/galleries/users_explain/text/fonts.py b/galleries/users_explain/text/fonts.py
index 7efb9a00aa09..067ed2f3932a 100644
--- a/galleries/users_explain/text/fonts.py
+++ b/galleries/users_explain/text/fonts.py
@@ -27,30 +27,35 @@
Matplotlib supports three font specifications (in addition to pdf 'core fonts',
which are explained later in the guide):
-.. list-table:: Type of Fonts
- :header-rows: 1
-
- * - Type 1 (PDF)
- - Type 3 (PDF/PS)
- - TrueType (PDF)
- * - One of the oldest types, introduced by Adobe
- - Similar to Type 1 in terms of introduction
- - Newer than previous types, used commonly today, introduced by Apple
- * - Restricted subset of PostScript, charstrings are in bytecode
- - Full PostScript language, allows embedding arbitrary code
- (in theory, even render fractals when rasterizing!)
- - Include a virtual machine that can execute code!
- * - These fonts support font hinting
- - Do not support font hinting
- - Hinting supported (virtual machine processes the "hints")
- * - Non-subsetted through Matplotlib
- - Subsetted via external module ttconv
- - Subsetted via external module
- `fontTools `__
+.. table:: Type of Fonts
+
+ +--------------------------+----------------------------+----------------------------+
+ | Type 1 (PDF with usetex) | Type 3 (PDF/PS) | TrueType (PDF) |
+ +==========================+============================+============================+
+ | One of the oldest types, | Similar to Type 1 in | Newer than previous types, |
+ | introduced by Adobe | terms of introduction | used commonly today, |
+ | | | introduced by Apple |
+ +--------------------------+----------------------------+----------------------------+
+ | Restricted subset of | Full PostScript language, | Includes a virtual machine |
+ | PostScript, charstrings | allows embedding arbitrary | that can execute code! |
+ | are in bytecode | code (in theory, even | |
+ | | render fractals when | |
+ | | rasterizing!) | |
+ +--------------------------+----------------------------+----------------------------+
+ | Supports font | Does not support font | Supports font hinting |
+ | hinting | hinting | (virtual machine processes |
+ | | | the "hints") |
+ +--------------------------+----------------------------+----------------------------+
+ | Subsetted by code in | Subsetted via external module |
+ | `matplotlib._type1font` | `fontTools `__ |
+ +--------------------------+----------------------------+----------------------------+
.. note::
Adobe disabled__ support for authoring with Type 1 fonts in January 2023.
+ Matplotlib uses Type 1 fonts for compatibility with TeX; when the usetex
+ feature is used with the PDF backend, Matplotlib reads the fonts used by
+ the TeX engine, which are usually Type 1.
__ https://helpx.adobe.com/fonts/kb/postscript-type-1-fonts-end-of-support.html
@@ -83,14 +88,12 @@
files, particularly with fonts with many glyphs such as those that support CJK
(Chinese/Japanese/Korean).
-The solution to this problem is to subset the fonts used in the document and
-only embed the glyphs actually used. This gets both vector text and small
-files sizes. Computing the subset of the font required and writing the new
-(reduced) font are both complex problem and thus Matplotlib relies on
-`fontTools `__ and a vendored fork
-of ttconv.
-
-Currently Type 3, Type 42, and TrueType fonts are subsetted. Type 1 fonts are not.
+To keep the output size reasonable while using vector fonts,
+Matplotlib embeds only the glyphs that are actually used in the document.
+This is known as font subsetting.
+Computing the font subset and writing the reduced font are both complex problems,
+which Matplotlib solves in most cases by using the
+`fontTools `__ library.
Core Fonts
^^^^^^^^^^
diff --git a/galleries/users_explain/text/text_props.py b/galleries/users_explain/text/text_props.py
index c5ae22c02d38..50eb53dccbdd 100644
--- a/galleries/users_explain/text/text_props.py
+++ b/galleries/users_explain/text/text_props.py
@@ -34,7 +34,7 @@
picker [None|float|bool|callable]
position (x, y)
rotation [ angle in degrees | ``'vertical'`` | ``'horizontal'`` ]
-size or fontsize [ size in points | relative size, e.g., ``'smaller'``, ``'x-large'`` ]
+ size or fontsize [ size in points | relative size, e.g., ``'small'``, ``'x-large'`` ]
style or fontstyle [ ``'normal'`` | ``'italic'`` | ``'oblique'`` ]
text string or anything printable with '%s' conversion
transform `~matplotlib.transforms.Transform` subclass
@@ -48,6 +48,9 @@
========================== ======================================================================================================================
+Text alignment
+==============
+
You can lay out text with the alignment arguments
``horizontalalignment``, ``verticalalignment``, and
``multialignment``. ``horizontalalignment`` controls whether the x
@@ -69,13 +72,13 @@
import matplotlib.patches as patches
# build a rectangle in axes coords
-left, width = .25, .5
-bottom, height = .25, .5
+left, width = 0.25, 0.5
+bottom, height = 0.25, 0.5
right = left + width
top = bottom + height
fig = plt.figure()
-ax = fig.add_axes([0, 0, 1, 1])
+ax = fig.add_axes((0, 0, 1, 1))
# axes coordinates: (0, 0) is bottom left and (1, 1) is upper right
p = patches.Rectangle(
@@ -144,6 +147,25 @@
plt.show()
# %%
+# Relative font sizes
+# ===================
+#
+# Font sizes can be specified in points, or as a string that indicates the size
+# relative to the default font size. The following are valid values for relative font sizes:
+#
+# ==================== ================================
+# Relative font size Scaling of default font size
+# ==================== ================================
+# xx-small 0.579
+# x-small 0.694
+# small 0.833
+# medium 1.000
+# large 1.200
+# x-large 1.440
+# xx-large 1.728
+# ==================== ================================
+#
+#
# ==============
# Default Font
# ==============
diff --git a/galleries/users_explain/toolkits/axisartist.rst b/galleries/users_explain/toolkits/axisartist.rst
index eff2b575a63f..7ff0897f23d8 100644
--- a/galleries/users_explain/toolkits/axisartist.rst
+++ b/galleries/users_explain/toolkits/axisartist.rst
@@ -50,7 +50,7 @@ To create an Axes, ::
import mpl_toolkits.axisartist as AA
fig = plt.figure()
- fig.add_axes([0.1, 0.1, 0.8, 0.8], axes_class=AA.Axes)
+ fig.add_axes((0.1, 0.1, 0.8, 0.8), axes_class=AA.Axes)
or to create a subplot ::
diff --git a/lib/matplotlib/__init__.py b/lib/matplotlib/__init__.py
index 9abc6c5a84dd..0b1b0e57dc6d 100644
--- a/lib/matplotlib/__init__.py
+++ b/lib/matplotlib/__init__.py
@@ -292,8 +292,8 @@ def set_loglevel(level):
- set the root logger handler's level, creating the handler
if it does not exist yet
- Typically, one should call ``set_loglevel("info")`` or
- ``set_loglevel("debug")`` to get additional debugging information.
+ Typically, one should call ``set_loglevel("INFO")`` or
+ ``set_loglevel("DEBUG")`` to get additional debugging information.
Users or applications that are installing their own logging handlers
may want to directly manipulate ``logging.getLogger('matplotlib')`` rather
@@ -301,8 +301,12 @@ def set_loglevel(level):
Parameters
----------
- level : {"notset", "debug", "info", "warning", "error", "critical"}
- The log level of the handler.
+ level : {"NOTSET", "DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"}
+ The log level as defined in `Python logging levels
+ `__.
+
+ For backwards compatibility, the levels are case-insensitive, but
+ the capitalized version is preferred in analogy to `logging.Logger.setLevel`.
Notes
-----
@@ -400,12 +404,15 @@ def impl(args, regex, min_ver=None, ignore_exit_code=False):
try:
output = subprocess.check_output(
args, stderr=subprocess.STDOUT,
- text=True, errors="replace")
+ text=True, errors="replace", timeout=30)
except subprocess.CalledProcessError as _cpe:
if ignore_exit_code:
output = _cpe.output
else:
raise ExecutableNotFoundError(str(_cpe)) from _cpe
+ except subprocess.TimeoutExpired as _te:
+ msg = f"Timed out running {cbook._pformat_subprocess(args)}"
+ raise ExecutableNotFoundError(msg) from _te
except OSError as _ose:
raise ExecutableNotFoundError(str(_ose)) from _ose
match = re.search(regex, output)
@@ -525,19 +532,30 @@ def _get_config_or_cache_dir(xdg_base_getter):
elif sys.platform.startswith(('linux', 'freebsd')):
# Only call _xdg_base_getter here so that MPLCONFIGDIR is tried first,
# as _xdg_base_getter can throw.
- configdir = Path(xdg_base_getter(), "matplotlib")
+ try:
+ configdir = Path(xdg_base_getter(), "matplotlib")
+ except RuntimeError: # raised if Path.home() is not available
+ pass
else:
- configdir = Path.home() / ".matplotlib"
- # Resolve the path to handle potential issues with inaccessible symlinks.
- configdir = configdir.resolve()
- try:
- configdir.mkdir(parents=True, exist_ok=True)
- except OSError as exc:
- _log.warning("mkdir -p failed for path %s: %s", configdir, exc)
+ try:
+ configdir = Path.home() / ".matplotlib"
+ except RuntimeError: # raised if Path.home() is not available
+ pass
+
+ if configdir:
+ # Resolve the path to handle potential issues with inaccessible symlinks.
+ configdir = configdir.resolve()
+ try:
+ configdir.mkdir(parents=True, exist_ok=True)
+ except OSError as exc:
+ _log.warning("mkdir -p failed for path %s: %s", configdir, exc)
+ else:
+ if os.access(str(configdir), os.W_OK) and configdir.is_dir():
+ return str(configdir)
+ _log.warning("%s is not a writable directory", configdir)
+ issue_msg = "the default path ({configdir})"
else:
- if os.access(str(configdir), os.W_OK) and configdir.is_dir():
- return str(configdir)
- _log.warning("%s is not a writable directory", configdir)
+ issue_msg = "resolving the home directory"
# If the config or cache directory cannot be created or is not a writable
# directory, create a temporary one.
try:
@@ -545,17 +563,17 @@ def _get_config_or_cache_dir(xdg_base_getter):
except OSError as exc:
raise OSError(
f"Matplotlib requires access to a writable cache directory, but there "
- f"was an issue with the default path ({configdir}), and a temporary "
+ f"was an issue with {issue_msg}, and a temporary "
f"directory could not be created; set the MPLCONFIGDIR environment "
f"variable to a writable directory") from exc
os.environ["MPLCONFIGDIR"] = tmpdir
atexit.register(shutil.rmtree, tmpdir)
_log.warning(
"Matplotlib created a temporary cache directory at %s because there was "
- "an issue with the default path (%s); it is highly recommended to set the "
+ "an issue with %s; it is highly recommended to set the "
"MPLCONFIGDIR environment variable to a writable directory, in particular to "
"speed up the import of Matplotlib and to better support multiprocessing.",
- tmpdir, configdir)
+ tmpdir, issue_msg)
return tmpdir
@@ -740,12 +758,11 @@ def __setitem__(self, key, val):
and val is rcsetup._auto_backend_sentinel
and "backend" in self):
return
+ valid_key = _api.check_getitem(
+ self.validate, rcParam=key, _error_cls=KeyError
+ )
try:
- cval = self.validate[key](val)
- except KeyError as err:
- raise KeyError(
- f"{key} is not a valid rc parameter (see rcParams.keys() for "
- f"a list of valid parameters)") from err
+ cval = valid_key(val)
except ValueError as ve:
raise ValueError(f"Key {key}: {ve}") from None
self._set(key, cval)
@@ -799,13 +816,13 @@ def find_all(self, pattern):
"""
pattern_re = re.compile(pattern)
- return RcParams((key, value)
- for key, value in self.items()
- if pattern_re.search(key))
+ return self.__class__(
+ (key, value) for key, value in self.items() if pattern_re.search(key)
+ )
def copy(self):
"""Copy this RcParams instance."""
- rccopy = RcParams()
+ rccopy = self.__class__()
for k in self: # Skip deprecations and revalidation.
rccopy._set(k, self._get(k))
return rccopy
@@ -1018,7 +1035,7 @@ def rc(group, **kwargs):
font = {'family' : 'monospace',
'weight' : 'bold',
- 'size' : 'larger'}
+ 'size' : 'large'}
rc('font', **font) # pass in the font dict as kwargs
This enables you to easily switch between several configurations. Use
@@ -1309,11 +1326,18 @@ def is_interactive():
return rcParams['interactive']
-def _val_or_rc(val, rc_name):
+def _val_or_rc(val, *rc_names):
"""
- If *val* is None, return ``mpl.rcParams[rc_name]``, otherwise return val.
+ If *val* is None, the first not-None value in ``mpl.rcParams[rc_names[i]]``.
+ If all are None returns ``mpl.rcParams[rc_names[-1]]``.
"""
- return val if val is not None else rcParams[rc_name]
+ if val is not None:
+ return val
+
+ for rc_name in rc_names[:-1]:
+ if rcParams[rc_name] is not None:
+ return rcParams[rc_name]
+ return rcParams[rc_names[-1]]
def _init_tests():
@@ -1338,8 +1362,8 @@ def _init_tests():
def _replacer(data, value):
"""
- Either returns ``data[value]`` or passes ``data`` back, converts either to
- a sequence.
+ Either returns ``data[value]`` or passes ``data`` back, converting any
+ ``MappingView`` to a sequence.
"""
try:
# if key isn't a string don't bother
diff --git a/lib/matplotlib/__init__.pyi b/lib/matplotlib/__init__.pyi
index 07019109f406..b20ee184149d 100644
--- a/lib/matplotlib/__init__.pyi
+++ b/lib/matplotlib/__init__.pyi
@@ -40,6 +40,8 @@ from packaging.version import Version
from matplotlib._api import MatplotlibDeprecationWarning
from typing import Any, Literal, NamedTuple, overload
+from matplotlib.typing import LogLevel
+
class _VersionInfo(NamedTuple):
major: int
@@ -52,7 +54,7 @@ __bibtex__: str
__version__: str
__version_info__: _VersionInfo
-def set_loglevel(level: str) -> None: ...
+def set_loglevel(level: LogLevel) -> None: ...
class _ExecInfo(NamedTuple):
executable: str
diff --git a/lib/matplotlib/_api/__init__.py b/lib/matplotlib/_api/__init__.py
index 47c32f701729..39496cfb0e82 100644
--- a/lib/matplotlib/_api/__init__.py
+++ b/lib/matplotlib/_api/__init__.py
@@ -10,6 +10,7 @@
"""
+import difflib
import functools
import itertools
import pathlib
@@ -174,12 +175,17 @@ def check_shape(shape, /, **kwargs):
)
-def check_getitem(mapping, /, **kwargs):
+def check_getitem(mapping, /, _error_cls=ValueError, **kwargs):
"""
*kwargs* must consist of a single *key, value* pair. If *key* is in
*mapping*, return ``mapping[value]``; else, raise an appropriate
ValueError.
+ Parameters
+ ----------
+ _error_cls :
+ Class of error to raise.
+
Examples
--------
>>> _api.check_getitem({"foo": "bar"}, arg=arg)
@@ -190,9 +196,14 @@ def check_getitem(mapping, /, **kwargs):
try:
return mapping[v]
except KeyError:
- raise ValueError(
- f"{v!r} is not a valid value for {k}; supported values are "
- f"{', '.join(map(repr, mapping))}") from None
+ if len(mapping) > 5:
+ if len(best := difflib.get_close_matches(v, mapping.keys(), cutoff=0.5)):
+ suggestion = f"Did you mean one of {best}?"
+ else:
+ suggestion = ""
+ else:
+ suggestion = f"Supported values are {', '.join(map(repr, mapping))}"
+ raise _error_cls(f"{v!r} is not a valid value for {k}. {suggestion}") from None
def caching_module_getattr(cls):
diff --git a/lib/matplotlib/_api/__init__.pyi b/lib/matplotlib/_api/__init__.pyi
index 9bf67110bb54..5db251c551e5 100644
--- a/lib/matplotlib/_api/__init__.pyi
+++ b/lib/matplotlib/_api/__init__.pyi
@@ -1,6 +1,6 @@
from collections.abc import Callable, Generator, Iterable, Mapping, Sequence
from typing import Any, TypeVar, overload
-from typing_extensions import Self # < Py 3.11
+from typing import Self
from numpy.typing import NDArray
@@ -42,7 +42,9 @@ def check_in_list(
values: Sequence[Any], /, *, _print_supported_values: bool = ..., **kwargs: Any
) -> None: ...
def check_shape(shape: tuple[int | None, ...], /, **kwargs: NDArray) -> None: ...
-def check_getitem(mapping: Mapping[Any, Any], /, **kwargs: Any) -> Any: ...
+def check_getitem(
+ mapping: Mapping[Any, _T], /, _error_cls: type[Exception], **kwargs: Any
+) -> _T: ...
def caching_module_getattr(cls: type) -> Callable[[str], Any]: ...
@overload
def define_aliases(
diff --git a/lib/matplotlib/_enums.py b/lib/matplotlib/_enums.py
index 75a09b7b5d8c..d85c5c5f03db 100644
--- a/lib/matplotlib/_enums.py
+++ b/lib/matplotlib/_enums.py
@@ -151,7 +151,7 @@ def demo():
import matplotlib.pyplot as plt
fig = plt.figure(figsize=(4, 1.2))
- ax = fig.add_axes([0, 0, 1, 0.8])
+ ax = fig.add_axes((0, 0, 1, 0.8))
ax.set_title('Cap style')
for x, style in enumerate(['butt', 'round', 'projecting']):
diff --git a/lib/matplotlib/_mathtext.py b/lib/matplotlib/_mathtext.py
index 19ddbb6d0883..2258c63e1bc2 100644
--- a/lib/matplotlib/_mathtext.py
+++ b/lib/matplotlib/_mathtext.py
@@ -8,6 +8,7 @@
import copy
import enum
import functools
+import itertools
import logging
import math
import os
@@ -409,14 +410,14 @@ def get_xheight(self, fontname: str, fontsize: float, dpi: float) -> float:
metrics = self.get_metrics(
fontname, mpl.rcParams['mathtext.default'], 'x', fontsize, dpi)
return metrics.iceberg
- xHeight = (pclt['xHeight'] / 64.0) * (fontsize / 12.0) * (dpi / 100.0)
- return xHeight
+ x_height = (pclt['xHeight'] / 64) * (fontsize / 12) * (dpi / 100)
+ return x_height
def get_underline_thickness(self, font: str, fontsize: float, dpi: float) -> float:
# This function used to grab underline thickness from the font
# metrics, but that information is just too un-reliable, so it
# is now hardcoded.
- return ((0.75 / 12.0) * fontsize * dpi) / 72.0
+ return ((0.75 / 12) * fontsize * dpi) / 72
def get_kern(self, font1: str, fontclass1: str, sym1: str, fontsize1: float,
font2: str, fontclass2: str, sym2: str, fontsize2: float,
@@ -1226,21 +1227,13 @@ def kern(self) -> None:
linked list.
"""
new_children = []
- num_children = len(self.children)
- if num_children:
- for i in range(num_children):
- elem = self.children[i]
- if i < num_children - 1:
- next = self.children[i + 1]
- else:
- next = None
-
- new_children.append(elem)
- kerning_distance = elem.get_kerning(next)
- if kerning_distance != 0.:
- kern = Kern(kerning_distance)
- new_children.append(kern)
- self.children = new_children
+ for elem0, elem1 in itertools.zip_longest(self.children, self.children[1:]):
+ new_children.append(elem0)
+ kerning_distance = elem0.get_kerning(elem1)
+ if kerning_distance != 0.:
+ kern = Kern(kerning_distance)
+ new_children.append(kern)
+ self.children = new_children
def hpack(self, w: float = 0.0,
m: T.Literal['additional', 'exactly'] = 'additional') -> None:
@@ -1534,11 +1527,9 @@ class AutoHeightChar(Hlist):
def __init__(self, c: str, height: float, depth: float, state: ParserState,
always: bool = False, factor: float | None = None):
- alternatives = state.fontset.get_sized_alternatives_for_symbol(
- state.font, c)
+ alternatives = state.fontset.get_sized_alternatives_for_symbol(state.font, c)
- xHeight = state.fontset.get_xheight(
- state.font, state.fontsize, state.dpi)
+ x_height = state.fontset.get_xheight(state.font, state.fontsize, state.dpi)
state = state.copy()
target_total = height + depth
@@ -1546,8 +1537,8 @@ def __init__(self, c: str, height: float, depth: float, state: ParserState,
state.font = fontname
char = Char(sym, state)
# Ensure that size 0 is chosen when the text is regular sized but
- # with descender glyphs by subtracting 0.2 * xHeight
- if char.height + char.depth >= target_total - 0.2 * xHeight:
+ # with descender glyphs by subtracting 0.2 * x_height
+ if char.height + char.depth >= target_total - 0.2 * x_height:
break
shift = 0.0
@@ -1574,8 +1565,7 @@ class AutoWidthChar(Hlist):
def __init__(self, c: str, width: float, state: ParserState, always: bool = False,
char_class: type[Char] = Char):
- alternatives = state.fontset.get_sized_alternatives_for_symbol(
- state.font, c)
+ alternatives = state.fontset.get_sized_alternatives_for_symbol(state.font, c)
state = state.copy()
for fontname, sym in alternatives:
@@ -2468,7 +2458,7 @@ def subsuper(self, s: str, loc: int, toks: ParseResults) -> T.Any:
state = self.get_state()
rule_thickness = state.fontset.get_underline_thickness(
state.font, state.fontsize, state.dpi)
- xHeight = state.fontset.get_xheight(
+ x_height = state.fontset.get_xheight(
state.font, state.fontsize, state.dpi)
if napostrophes:
@@ -2537,24 +2527,21 @@ def subsuper(self, s: str, loc: int, toks: ParseResults) -> T.Any:
nucleus = Hlist([nucleus])
# Handle regular sub/superscripts
- constants = _get_font_constant_set(state)
+ consts = _get_font_constant_set(state)
lc_height = last_char.height
lc_baseline = 0
if self.is_dropsub(last_char):
lc_baseline = last_char.depth
# Compute kerning for sub and super
- superkern = constants.delta * xHeight
- subkern = constants.delta * xHeight
+ superkern = consts.delta * x_height
+ subkern = consts.delta * x_height
if self.is_slanted(last_char):
- superkern += constants.delta * xHeight
- superkern += (constants.delta_slanted *
- (lc_height - xHeight * 2. / 3.))
+ superkern += consts.delta * x_height
+ superkern += consts.delta_slanted * (lc_height - x_height * 2 / 3)
if self.is_dropsub(last_char):
- subkern = (3 * constants.delta -
- constants.delta_integral) * lc_height
- superkern = (3 * constants.delta +
- constants.delta_integral) * lc_height
+ subkern = (3 * consts.delta - consts.delta_integral) * lc_height
+ superkern = (3 * consts.delta + consts.delta_integral) * lc_height
else:
subkern = 0
@@ -2567,28 +2554,28 @@ def subsuper(self, s: str, loc: int, toks: ParseResults) -> T.Any:
x = Hlist([Kern(subkern), T.cast(Node, sub)])
x.shrink()
if self.is_dropsub(last_char):
- shift_down = lc_baseline + constants.subdrop * xHeight
+ shift_down = lc_baseline + consts.subdrop * x_height
else:
- shift_down = constants.sub1 * xHeight
+ shift_down = consts.sub1 * x_height
x.shift_amount = shift_down
else:
x = Hlist([Kern(superkern), super])
x.shrink()
if self.is_dropsub(last_char):
- shift_up = lc_height - constants.subdrop * xHeight
+ shift_up = lc_height - consts.subdrop * x_height
else:
- shift_up = constants.sup1 * xHeight
+ shift_up = consts.sup1 * x_height
if sub is None:
x.shift_amount = -shift_up
else: # Both sub and superscript
y = Hlist([Kern(subkern), sub])
y.shrink()
if self.is_dropsub(last_char):
- shift_down = lc_baseline + constants.subdrop * xHeight
+ shift_down = lc_baseline + consts.subdrop * x_height
else:
- shift_down = constants.sub2 * xHeight
+ shift_down = consts.sub2 * x_height
# If sub and superscript collide, move super up
- clr = (2.0 * rule_thickness -
+ clr = (2 * rule_thickness -
((shift_up - x.depth) - (y.height - shift_down)))
if clr > 0.:
shift_up += clr
@@ -2599,7 +2586,7 @@ def subsuper(self, s: str, loc: int, toks: ParseResults) -> T.Any:
x.shift_amount = shift_down
if not self.is_dropsub(last_char):
- x.width += constants.script_space * xHeight
+ x.width += consts.script_space * x_height
# Do we need to add a space after the nucleus?
# To find out, check the flag set by operatorname
@@ -2624,12 +2611,13 @@ def _genfrac(self, ldelim: str, rdelim: str, rule: float | None, style: _MathSty
width = max(num.width, den.width)
cnum.hpack(width, 'exactly')
cden.hpack(width, 'exactly')
- vlist = Vlist([cnum, # numerator
- Vbox(0, thickness * 2.0), # space
- Hrule(state, rule), # rule
- Vbox(0, thickness * 2.0), # space
- cden # denominator
- ])
+ vlist = Vlist([
+ cnum, # numerator
+ Vbox(0, 2 * thickness), # space
+ Hrule(state, rule), # rule
+ Vbox(0, 2 * thickness), # space
+ cden, # denominator
+ ])
# Shift so the fraction line sits in the middle of the
# equals sign
@@ -2637,20 +2625,12 @@ def _genfrac(self, ldelim: str, rdelim: str, rule: float | None, style: _MathSty
state.font, mpl.rcParams['mathtext.default'],
'=', state.fontsize, state.dpi)
shift = (cden.height -
- ((metrics.ymax + metrics.ymin) / 2 -
- thickness * 3.0))
+ ((metrics.ymax + metrics.ymin) / 2 - 3 * thickness))
vlist.shift_amount = shift
- result = [Hlist([vlist, Hbox(thickness * 2.)])]
+ result: list[Box | Char | str] = [Hlist([vlist, Hbox(2 * thickness)])]
if ldelim or rdelim:
- if ldelim == '':
- ldelim = '.'
- if rdelim == '':
- rdelim = '.'
- return self._auto_sized_delimiter(ldelim,
- T.cast(list[Box | Char | str],
- result),
- rdelim)
+ return self._auto_sized_delimiter(ldelim or ".", result, rdelim or ".")
return result
def style_literal(self, toks: ParseResults) -> T.Any:
@@ -2719,7 +2699,7 @@ def sqrt(self, toks: ParseResults) -> T.Any:
# Determine the height of the body, and add a little extra to
# the height so it doesn't seem cramped
- height = body.height - body.shift_amount + thickness * 5.0
+ height = body.height - body.shift_amount + 5 * thickness
depth = body.depth + body.shift_amount
check = AutoHeightChar(r'\__sqrt__', height, depth, state, always=True)
height = check.height - check.shift_amount
@@ -2729,13 +2709,13 @@ def sqrt(self, toks: ParseResults) -> T.Any:
padded_body = Hlist([Hbox(2 * thickness), body, Hbox(2 * thickness)])
rightside = Vlist([Hrule(state), Glue('fill'), padded_body])
# Stretch the glue between the hrule and the body
- rightside.vpack(height + (state.fontsize * state.dpi) / (100.0 * 12.0),
+ rightside.vpack(height + (state.fontsize * state.dpi) / (100 * 12),
'exactly', depth)
# Add the root and shift it upward so it is above the tick.
# The value of 0.6 is a hard-coded hack ;)
if not root:
- root = Box(check.width * 0.5, 0., 0.)
+ root = Box(0.5 * check.width, 0., 0.)
else:
root = Hlist(root)
root.shrink()
@@ -2744,11 +2724,12 @@ def sqrt(self, toks: ParseResults) -> T.Any:
root_vlist = Vlist([Hlist([root])])
root_vlist.shift_amount = -height * 0.6
- hlist = Hlist([root_vlist, # Root
- # Negative kerning to put root over tick
- Kern(-check.width * 0.5),
- check, # Check
- rightside]) # Body
+ hlist = Hlist([
+ root_vlist, # Root
+ Kern(-0.5 * check.width), # Negative kerning to put root over tick
+ check, # Check
+ rightside, # Body
+ ])
return [hlist]
def overline(self, toks: ParseResults) -> T.Any:
@@ -2757,14 +2738,14 @@ def overline(self, toks: ParseResults) -> T.Any:
state = self.get_state()
thickness = state.get_current_underline_thickness()
- height = body.height - body.shift_amount + thickness * 3.0
+ height = body.height - body.shift_amount + 3 * thickness
depth = body.depth + body.shift_amount
# Place overline above body
rightside = Vlist([Hrule(state), Glue('fill'), Hlist([body])])
# Stretch the glue between the hrule and the body
- rightside.vpack(height + (state.fontsize * state.dpi) / (100.0 * 12.0),
+ rightside.vpack(height + (state.fontsize * state.dpi) / (100 * 12),
'exactly', depth)
hlist = Hlist([rightside])
@@ -2810,10 +2791,7 @@ def _auto_sized_delimiter(self, front: str,
def auto_delim(self, toks: ParseResults) -> T.Any:
return self._auto_sized_delimiter(
- toks["left"],
- # if "mid" in toks ... can be removed when requiring pyparsing 3.
- toks["mid"].as_list() if "mid" in toks else [],
- toks["right"])
+ toks["left"], toks["mid"].as_list(), toks["right"])
def boldsymbol(self, toks: ParseResults) -> T.Any:
self.push_state()
diff --git a/lib/matplotlib/animation.py b/lib/matplotlib/animation.py
index c6ff7702d992..8756cb0c1439 100644
--- a/lib/matplotlib/animation.py
+++ b/lib/matplotlib/animation.py
@@ -1,5 +1,6 @@
import abc
import base64
+import collections
import contextlib
from io import BytesIO, TextIOWrapper
import itertools
@@ -1708,13 +1709,13 @@ def iter_frames(frames=frames):
self._cache_frame_data = cache_frame_data
# Needs to be initialized so the draw functions work without checking
- self._save_seq = []
+ self._save_seq = collections.deque([], self._save_count)
super().__init__(fig, **kwargs)
# Need to reset the saved seq, since right now it will contain data
# for a single frame from init, which is not what we want.
- self._save_seq = []
+ self._save_seq.clear()
def new_frame_seq(self):
# Use the generating function to generate a new frame sequence
@@ -1727,8 +1728,7 @@ def new_saved_frame_seq(self):
if self._save_seq:
# While iterating we are going to update _save_seq
# so make a copy to safely iterate over
- self._old_saved_seq = list(self._save_seq)
- return iter(self._old_saved_seq)
+ return iter([*self._save_seq])
else:
if self._save_count is None:
frame_seq = self.new_frame_seq()
@@ -1773,13 +1773,12 @@ def _init_draw(self):
'return a sequence of Artist objects.')
for a in self._drawn_artists:
a.set_animated(self._blit)
- self._save_seq = []
+ self._save_seq.clear()
def _draw_frame(self, framedata):
if self._cache_frame_data:
# Save the data for potential saving of movies.
self._save_seq.append(framedata)
- self._save_seq = self._save_seq[-self._save_count:]
# Call the func with framedata and args. If blitting is desired,
# func needs to return a sequence of any artists that were modified.
diff --git a/lib/matplotlib/artist.py b/lib/matplotlib/artist.py
index fd35b312835a..eaaae43e283a 100644
--- a/lib/matplotlib/artist.py
+++ b/lib/matplotlib/artist.py
@@ -321,13 +321,12 @@ def stale(self, val):
def get_window_extent(self, renderer=None):
"""
- Get the artist's bounding box in display space.
+ Get the artist's bounding box in display space, ignoring clipping.
The bounding box's width and height are non-negative.
- Subclasses should override for inclusion in the bounding box
- "tight" calculation. Default is to return an empty bounding
- box at 0, 0.
+ Subclasses should override for inclusion in the bounding box "tight"
+ calculation. Default is to return an empty bounding box at 0, 0.
.. warning::
@@ -341,28 +340,40 @@ def get_window_extent(self, renderer=None):
screen render incorrectly when saved to file.
To get accurate results you may need to manually call
- `matplotlib.figure.Figure.savefig` or
- `matplotlib.figure.Figure.draw_without_rendering` to have Matplotlib
- compute the rendered size.
+ `~.Figure.savefig` or `~.Figure.draw_without_rendering` to have
+ Matplotlib compute the rendered size.
+ Parameters
+ ----------
+ renderer : `~matplotlib.backend_bases.RendererBase`, optional
+ Renderer used to draw the figure (i.e. ``fig.canvas.get_renderer()``).
+
+ See Also
+ --------
+ .Artist.get_tightbbox :
+ Get the artist bounding box, taking clipping into account.
"""
return Bbox([[0, 0], [0, 0]])
def get_tightbbox(self, renderer=None):
"""
- Like `.Artist.get_window_extent`, but includes any clipping.
+ Get the artist's bounding box in display space, taking clipping into account.
Parameters
----------
- renderer : `~matplotlib.backend_bases.RendererBase` subclass, optional
- renderer that will be used to draw the figures (i.e.
- ``fig.canvas.get_renderer()``)
+ renderer : `~matplotlib.backend_bases.RendererBase`, optional
+ Renderer used to draw the figure (i.e. ``fig.canvas.get_renderer()``).
Returns
-------
`.Bbox` or None
- The enclosing bounding box (in figure pixel coordinates).
- Returns None if clipping results in no intersection.
+ The enclosing bounding box (in figure pixel coordinates), or None
+ if clipping results in no intersection.
+
+ See Also
+ --------
+ .Artist.get_window_extent :
+ Get the artist bounding box, ignoring clipping.
"""
bbox = self.get_window_extent(renderer)
if self.get_clip_on():
diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py
index b4ed7ae22d35..6da0e925ab4f 100644
--- a/lib/matplotlib/axes/_axes.py
+++ b/lib/matplotlib/axes/_axes.py
@@ -1183,7 +1183,7 @@ def hlines(self, y, xmin, xmax, colors=None, linestyles='solid',
if self.name == "rectilinear":
datalim = lines.get_datalim(self.transData)
t = lines.get_transform()
- updatex, updatey = t.contains_branch_seperately(self.transData)
+ updatex, updatey = t.contains_branch_separately(self.transData)
minx = np.nanmin(datalim.xmin)
maxx = np.nanmax(datalim.xmax)
miny = np.nanmin(datalim.ymin)
@@ -1275,7 +1275,7 @@ def vlines(self, x, ymin, ymax, colors=None, linestyles='solid',
if self.name == "rectilinear":
datalim = lines.get_datalim(self.transData)
t = lines.get_transform()
- updatex, updatey = t.contains_branch_seperately(self.transData)
+ updatex, updatey = t.contains_branch_separately(self.transData)
minx = np.nanmin(datalim.xmin)
maxx = np.nanmax(datalim.xmax)
miny = np.nanmin(datalim.ymin)
@@ -2296,8 +2296,8 @@ def _parse_bar_color_args(self, kwargs):
facecolor = mcolors.to_rgba_array(facecolor)
except ValueError as err:
raise ValueError(
- "'facecolor' or 'color' argument must be a valid color or"
- "sequence of colors."
+ "'facecolor' or 'color' argument must be a valid color or "
+ "sequence of colors."
) from err
return facecolor, edgecolor
@@ -2966,7 +2966,7 @@ def sign(x):
@_preprocess_data()
@_docstring.interpd
- def broken_barh(self, xranges, yrange, **kwargs):
+ def broken_barh(self, xranges, yrange, align="bottom", **kwargs):
"""
Plot a horizontal sequence of rectangles.
@@ -2979,8 +2979,16 @@ def broken_barh(self, xranges, yrange, **kwargs):
The x-positions and extents of the rectangles. For each tuple
(*xmin*, *xwidth*) a rectangle is drawn from *xmin* to *xmin* +
*xwidth*.
- yrange : (*ymin*, *yheight*)
+ yrange : (*ypos*, *yheight*)
The y-position and extent for all the rectangles.
+ align : {"bottom", "center", "top"}, default: 'bottom'
+ The alignment of the yrange with respect to the y-position. One of:
+
+ - "bottom": Resulting y-range [ypos, ypos + yheight]
+ - "center": Resulting y-range [ypos - yheight/2, ypos + yheight/2]
+ - "top": Resulting y-range [ypos - yheight, ypos]
+
+ .. versionadded:: 3.11
Returns
-------
@@ -3015,7 +3023,15 @@ def broken_barh(self, xranges, yrange, **kwargs):
vertices = []
y0, dy = yrange
- y0, y1 = self.convert_yunits((y0, y0 + dy))
+
+ _api.check_in_list(['bottom', 'center', 'top'], align=align)
+ if align == "bottom":
+ y0, y1 = self.convert_yunits((y0, y0 + dy))
+ elif align == "center":
+ y0, y1 = self.convert_yunits((y0 - dy/2, y0 + dy/2))
+ else:
+ y0, y1 = self.convert_yunits((y0 - dy, y0))
+
for xr in xranges: # convert the absolute values, not the x and dx
try:
x0, dx = xr
@@ -3027,8 +3043,7 @@ def broken_barh(self, xranges, yrange, **kwargs):
vertices.append([(x0, y0), (x0, y1), (x1, y1), (x1, y0)])
col = mcoll.PolyCollection(np.array(vertices), **kwargs)
- self.add_collection(col, autolim=True)
- self._request_autoscale_view()
+ self.add_collection(col)
return col
@@ -3436,6 +3451,9 @@ def stem(self, *args, linefmt=None, markerfmt=None, basefmt=None, bottom=0,
else: # horizontal
heads, locs = self._process_unit_info([("x", heads), ("y", locs)])
+ heads = cbook._check_1d(heads)
+ locs = cbook._check_1d(locs)
+
# resolve line format
if linefmt is None:
linefmt = args[0] if len(args) > 0 else "C0-"
@@ -5334,7 +5352,6 @@ def scatter(self, x, y, s=None, c=None, marker=None, cmap=None, norm=None,
self.set_ymargin(0.05)
self.add_collection(collection)
- self._request_autoscale_view()
return collection
@@ -5804,8 +5821,7 @@ def quiver(self, *args, **kwargs):
# Make sure units are handled for x and y values
args = self._quiver_units(args, kwargs)
q = mquiver.Quiver(self, *args, **kwargs)
- self.add_collection(q, autolim=True)
- self._request_autoscale_view()
+ self.add_collection(q)
return q
# args can be some combination of X, Y, U, V, C and all should be replaced
@@ -5816,8 +5832,7 @@ def barbs(self, *args, **kwargs):
# Make sure units are handled for x and y values
args = self._quiver_units(args, kwargs)
b = mquiver.Barbs(self, *args, **kwargs)
- self.add_collection(b, autolim=True)
- self._request_autoscale_view()
+ self.add_collection(b)
return b
# Uses a custom implementation of data-kwarg handling in
@@ -5977,7 +5992,6 @@ def _fill_between_x_or_y(
where=where, interpolate=interpolate, step=step, **kwargs)
self.add_collection(collection)
- self._request_autoscale_view()
return collection
def _fill_between_process_units(self, ind_dir, dep_dir, ind, dep1, dep2, **kwargs):
@@ -6795,7 +6809,7 @@ def _update_pcolor_lims(self, collection, coords):
hasattr(t, '_as_mpl_transform')):
t = t._as_mpl_transform(self.axes)
- if t and any(t.contains_branch_seperately(self.transData)):
+ if t and any(t.contains_branch_separately(self.transData)):
trans_to_data = t - self.transData
coords = trans_to_data.transform(coords)
diff --git a/lib/matplotlib/axes/_axes.pyi b/lib/matplotlib/axes/_axes.pyi
index 0008363b8220..69d251aa21f7 100644
--- a/lib/matplotlib/axes/_axes.pyi
+++ b/lib/matplotlib/axes/_axes.pyi
@@ -36,7 +36,7 @@ from collections.abc import Callable, Iterable, Sequence
from typing import Any, Literal, overload
import numpy as np
from numpy.typing import ArrayLike
-from matplotlib.typing import ColorType, MarkerType, LineStyleType
+from matplotlib.typing import ColorType, MarkerType, LegendLocType, LineStyleType
import pandas as pd
@@ -65,13 +65,16 @@ class Axes(_AxesBase):
@overload
def legend(self) -> Legend: ...
@overload
- def legend(self, handles: Iterable[Artist | tuple[Artist, ...]], labels: Iterable[str], **kwargs) -> Legend: ...
+ def legend(self, handles: Iterable[Artist | tuple[Artist, ...]], labels: Iterable[str],
+ *, loc: LegendLocType | None = ..., **kwargs) -> Legend: ...
@overload
- def legend(self, *, handles: Iterable[Artist | tuple[Artist, ...]], **kwargs) -> Legend: ...
+ def legend(self, *, handles: Iterable[Artist | tuple[Artist, ...]],
+ loc: LegendLocType | None = ..., **kwargs) -> Legend: ...
@overload
- def legend(self, labels: Iterable[str], **kwargs) -> Legend: ...
+ def legend(self, labels: Iterable[str],
+ *, loc: LegendLocType | None = ..., **kwargs) -> Legend: ...
@overload
- def legend(self, **kwargs) -> Legend: ...
+ def legend(self, *, loc: LegendLocType | None = ..., **kwargs) -> Legend: ...
def inset_axes(
self,
@@ -268,6 +271,7 @@ class Axes(_AxesBase):
self,
xranges: Sequence[tuple[float, float]],
yrange: tuple[float, float],
+ align: Literal["bottom", "center", "top"] = ...,
*,
data=...,
**kwargs
diff --git a/lib/matplotlib/axes/_base.py b/lib/matplotlib/axes/_base.py
index 15f8e97b449f..42f6e51786bf 100644
--- a/lib/matplotlib/axes/_base.py
+++ b/lib/matplotlib/axes/_base.py
@@ -724,7 +724,6 @@ def __init__(self, fig,
self.fmt_ydata = None
self.set_navigate(True)
- self.set_navigate_mode(None)
if xscale:
self.set_xscale(xscale)
@@ -897,7 +896,7 @@ def _request_autoscale_view(self, axis="all", tight=None):
Mark a single axis, or all of them, as stale wrt. autoscaling.
No computation is performed until the next autoscaling; thus, separate
- calls to control individual axises incur negligible performance cost.
+ calls to control individual `Axis`s incur negligible performance cost.
Parameters
----------
@@ -2185,9 +2184,9 @@ def axis(self, arg=None, /, *, emit=True, **kwargs):
xlim = self.get_xlim()
ylim = self.get_ylim()
edge_size = max(np.diff(xlim), np.diff(ylim))[0]
- self.set_xlim([xlim[0], xlim[0] + edge_size],
+ self.set_xlim(xlim[0], xlim[0] + edge_size,
emit=emit, auto=False)
- self.set_ylim([ylim[0], ylim[0] + edge_size],
+ self.set_ylim(ylim[0], ylim[0] + edge_size,
emit=emit, auto=False)
else:
raise ValueError(f"Unrecognized string {arg!r} to axis; "
@@ -2337,6 +2336,23 @@ def add_child_axes(self, ax):
def add_collection(self, collection, autolim=True):
"""
Add a `.Collection` to the Axes; return the collection.
+
+ Parameters
+ ----------
+ collection : `.Collection`
+ The collection to add.
+ autolim : bool
+ Whether to update data and view limits.
+
+ .. versionchanged:: 3.11
+
+ This now also updates the view limits, making explicit
+ calls to `~.Axes.autoscale_view` unnecessary.
+
+ As an implementation detail, the value "_datalim_only" is
+ supported to smooth the internal transition from pre-3.11
+ behavior. This is not a public interface and will be removed
+ again in the future.
"""
_api.check_isinstance(mcoll.Collection, collection=collection)
if not collection.get_label():
@@ -2361,7 +2377,19 @@ def add_collection(self, collection, autolim=True):
# the call so that self.dataLim will update its own minpos.
# This ensures that log scales see the correct minimum.
points = np.concatenate([points, [datalim.minpos]])
- self.update_datalim(points)
+ # only update the dataLim for x/y if the collection uses transData
+ # in this direction.
+ x_is_data, y_is_data = (collection.get_transform()
+ .contains_branch_separately(self.transData))
+ ox_is_data, oy_is_data = (collection.get_offset_transform()
+ .contains_branch_separately(self.transData))
+ self.update_datalim(
+ points,
+ updatex=x_is_data or ox_is_data,
+ updatey=y_is_data or oy_is_data,
+ )
+ if autolim != "_datalim_only":
+ self._request_autoscale_view()
self.stale = True
return collection
@@ -2423,7 +2451,7 @@ def _update_line_limits(self, line):
if line_trf == self.transData:
data_path = path
- elif any(line_trf.contains_branch_seperately(self.transData)):
+ elif any(line_trf.contains_branch_separately(self.transData)):
# Compute the transform from line coordinates to data coordinates.
trf_to_data = line_trf - self.transData
# If transData is affine we can use the cached non-affine component
@@ -2446,7 +2474,7 @@ def _update_line_limits(self, line):
if not data_path.vertices.size:
return
- updatex, updatey = line_trf.contains_branch_seperately(self.transData)
+ updatex, updatey = line_trf.contains_branch_separately(self.transData)
if self.name != "rectilinear":
# This block is mostly intended to handle axvline in polar plots,
# for which updatey would otherwise be True.
@@ -2499,7 +2527,7 @@ def _update_patch_limits(self, patch):
vertices = np.vstack(vertices)
patch_trf = patch.get_transform()
- updatex, updatey = patch_trf.contains_branch_seperately(self.transData)
+ updatex, updatey = patch_trf.contains_branch_separately(self.transData)
if not (updatex or updatey):
return
if self.name != "rectilinear":
@@ -4177,17 +4205,22 @@ def get_navigate_mode(self):
"""
Get the navigation toolbar button status: 'PAN', 'ZOOM', or None.
"""
- return self._navigate_mode
+ toolbar = self.figure.canvas.toolbar
+ if toolbar:
+ return None if toolbar.mode.name == "NONE" else toolbar.mode.name
+ manager = self.figure.canvas.manager
+ if manager and manager.toolmanager:
+ mode = manager.toolmanager.active_toggle.get("default")
+ return None if mode is None else mode.upper()
+ @_api.deprecated("3.11")
def set_navigate_mode(self, b):
"""
Set the navigation toolbar button status.
.. warning::
This is not a user-API function.
-
"""
- self._navigate_mode = b
def _get_view(self):
"""
@@ -4749,14 +4782,25 @@ def label_outer(self, remove_inner_ticks=False):
self._label_outer_yaxis(skip_non_rectangular_axes=False,
remove_inner_ticks=remove_inner_ticks)
+ def _get_subplotspec_with_optional_colorbar(self):
+ """
+ Return the subplotspec for this Axes, except that if this Axes has been
+ moved to a subgridspec to make room for a colorbar, then return the
+ subplotspec that encloses both this Axes and the colorbar Axes.
+ """
+ ss = self.get_subplotspec()
+ if any(cax.get_subplotspec() for cax in self._colorbars):
+ ss = ss.get_gridspec()._subplot_spec
+ return ss
+
def _label_outer_xaxis(self, *, skip_non_rectangular_axes,
remove_inner_ticks=False):
# see documentation in label_outer.
if skip_non_rectangular_axes and not isinstance(self.patch,
mpl.patches.Rectangle):
return
- ss = self.get_subplotspec()
- if not ss:
+ ss = self._get_subplotspec_with_optional_colorbar()
+ if ss is None:
return
label_position = self.xaxis.get_label_position()
if not ss.is_first_row(): # Remove top label/ticklabels/offsettext.
@@ -4782,8 +4826,8 @@ def _label_outer_yaxis(self, *, skip_non_rectangular_axes,
if skip_non_rectangular_axes and not isinstance(self.patch,
mpl.patches.Rectangle):
return
- ss = self.get_subplotspec()
- if not ss:
+ ss = self._get_subplotspec_with_optional_colorbar()
+ if ss is None:
return
label_position = self.yaxis.get_label_position()
if not ss.is_first_col(): # Remove left label/ticklabels/offsettext.
diff --git a/lib/matplotlib/axes/_base.pyi b/lib/matplotlib/axes/_base.pyi
index 4933d0d1e236..5a5225eb8ba1 100644
--- a/lib/matplotlib/axes/_base.pyi
+++ b/lib/matplotlib/axes/_base.pyi
@@ -225,7 +225,7 @@ class _AxesBase(martist.Artist):
ymin: float | None = ...,
ymax: float | None = ...
) -> tuple[float, float, float, float]: ...
- def get_legend(self) -> Legend: ...
+ def get_legend(self) -> Legend | None: ...
def get_images(self) -> list[AxesImage]: ...
def get_lines(self) -> list[Line2D]: ...
def get_xaxis(self) -> XAxis: ...
@@ -234,7 +234,7 @@ class _AxesBase(martist.Artist):
def add_artist(self, a: Artist) -> Artist: ...
def add_child_axes(self, ax: _AxesBase) -> _AxesBase: ...
def add_collection(
- self, collection: Collection, autolim: bool = ...
+ self, collection: Collection, autolim: bool | Literal["_datalim_only"] = ...
) -> Collection: ...
def add_image(self, image: AxesImage) -> AxesImage: ...
def add_line(self, line: Line2D) -> Line2D: ...
diff --git a/lib/matplotlib/axes/_secondary_axes.py b/lib/matplotlib/axes/_secondary_axes.py
index 15a1970fa4a6..08e706bba245 100644
--- a/lib/matplotlib/axes/_secondary_axes.py
+++ b/lib/matplotlib/axes/_secondary_axes.py
@@ -1,3 +1,4 @@
+import functools
import numbers
import numpy as np
@@ -145,10 +146,25 @@ def apply_aspect(self, position=None):
self._set_lims()
super().apply_aspect(position)
- @_docstring.copy(Axis.set_ticks)
- def set_ticks(self, ticks, labels=None, *, minor=False, **kwargs):
- ret = self._axis.set_ticks(ticks, labels, minor=minor, **kwargs)
- self.stale = True
+ @functools.wraps(_AxesBase.set_xticks)
+ def set_xticks(self, *args, **kwargs):
+ if self._orientation == "y":
+ raise TypeError("Cannot set xticks on a secondary y-axis")
+ ret = super().set_xticks(*args, **kwargs)
+ self._ticks_set = True
+ return ret
+
+ @functools.wraps(_AxesBase.set_yticks)
+ def set_yticks(self, *args, **kwargs):
+ if self._orientation == "x":
+ raise TypeError("Cannot set yticks on a secondary x-axis")
+ ret = super().set_yticks(*args, **kwargs)
+ self._ticks_set = True
+ return ret
+
+ @functools.wraps(Axis.set_ticks)
+ def set_ticks(self, *args, **kwargs):
+ ret = self._axis.set_ticks(*args, **kwargs)
self._ticks_set = True
return ret
diff --git a/lib/matplotlib/axes/_secondary_axes.pyi b/lib/matplotlib/axes/_secondary_axes.pyi
index afb429f740c4..92bba590a4fa 100644
--- a/lib/matplotlib/axes/_secondary_axes.pyi
+++ b/lib/matplotlib/axes/_secondary_axes.pyi
@@ -29,6 +29,22 @@ class SecondaryAxis(_AxesBase):
location: Literal["top", "bottom", "right", "left"] | float,
transform: Transform | None = ...
) -> None: ...
+ def set_xticks(
+ self,
+ ticks: ArrayLike,
+ labels: Iterable[str] | None = ...,
+ *,
+ minor: bool = ...,
+ **kwargs
+ ) -> list[Tick]: ...
+ def set_yticks(
+ self,
+ ticks: ArrayLike,
+ labels: Iterable[str] | None = ...,
+ *,
+ minor: bool = ...,
+ **kwargs
+ ) -> list[Tick]: ...
def set_ticks(
self,
ticks: ArrayLike,
diff --git a/lib/matplotlib/axis.py b/lib/matplotlib/axis.py
index 19096fc29d3e..ba17c634ac5c 100644
--- a/lib/matplotlib/axis.py
+++ b/lib/matplotlib/axis.py
@@ -125,16 +125,33 @@ def __init__(
zorder = mlines.Line2D.zorder
self._zorder = zorder
- grid_color = mpl._val_or_rc(grid_color, "grid.color")
- grid_linestyle = mpl._val_or_rc(grid_linestyle, "grid.linestyle")
- grid_linewidth = mpl._val_or_rc(grid_linewidth, "grid.linewidth")
+ grid_color = mpl._val_or_rc(
+ grid_color,
+ f"grid.{major_minor}.color",
+ "grid.color",
+ )
+ grid_linestyle = mpl._val_or_rc(
+ grid_linestyle,
+ f"grid.{major_minor}.linestyle",
+ "grid.linestyle",
+ )
+ grid_linewidth = mpl._val_or_rc(
+ grid_linewidth,
+ 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.rcParams["grid.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",
+ )
+
grid_kw = {k[5:]: v for k, v in kwargs.items() if k != "rotation_mode"}
self.tick1line = mlines.Line2D(
@@ -1313,14 +1330,14 @@ def _update_ticks(self):
return ticks_to_draw
- def _get_ticklabel_bboxes(self, ticks, renderer=None):
+ def _get_ticklabel_bboxes(self, ticks, renderer):
"""Return lists of bboxes for ticks' label1's and label2's."""
- if renderer is None:
- renderer = self.get_figure(root=True)._get_renderer()
return ([tick.label1.get_window_extent(renderer)
- for tick in ticks if tick.label1.get_visible()],
+ for tick in ticks
+ if tick.label1.get_visible() and tick.label1.get_in_layout()],
[tick.label2.get_window_extent(renderer)
- for tick in ticks if tick.label2.get_visible()])
+ for tick in ticks
+ if tick.label2.get_visible() and tick.label2.get_in_layout()])
def get_tightbbox(self, renderer=None, *, for_layout_only=False):
"""
diff --git a/lib/matplotlib/axis.pyi b/lib/matplotlib/axis.pyi
index 6119b946fd7b..4bcfb1e1cfb7 100644
--- a/lib/matplotlib/axis.pyi
+++ b/lib/matplotlib/axis.pyi
@@ -1,7 +1,7 @@
from collections.abc import Callable, Iterable, Sequence
import datetime
from typing import Any, Literal, overload
-from typing_extensions import Self # < Py 3.11
+from typing import Self
import numpy as np
from numpy.typing import ArrayLike
diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py
index 527d8c010710..8107471955fe 100644
--- a/lib/matplotlib/backend_bases.py
+++ b/lib/matplotlib/backend_bases.py
@@ -76,6 +76,7 @@
'tif': 'Tagged Image File Format',
'tiff': 'Tagged Image File Format',
'webp': 'WebP Image Format',
+ 'avif': 'AV1 Image File Format',
}
_default_backends = {
'eps': 'matplotlib.backends.backend_ps',
@@ -93,6 +94,7 @@
'tif': 'matplotlib.backends.backend_agg',
'tiff': 'matplotlib.backends.backend_agg',
'webp': 'matplotlib.backends.backend_agg',
+ 'avif': 'matplotlib.backends.backend_agg',
}
@@ -1411,6 +1413,23 @@ def __init__(self, name, canvas, x, y, button=None, key=None,
self.step = step
self.dblclick = dblclick
+ @classmethod
+ def _from_ax_coords(cls, name, ax, xy, *args, **kwargs):
+ """
+ Generate a synthetic event at a given axes coordinate.
+
+ This method is intended for creating events during testing. The event
+ can be emitted by calling its ``_process()`` method.
+
+ args and kwargs are mapped to `.MouseEvent.__init__` parameters,
+ starting with `button`.
+ """
+ x, y = ax.transData.transform(xy)
+ event = cls(name, ax.figure.canvas, x, y, *args, **kwargs)
+ event.inaxes = ax
+ event.xdata, event.ydata = xy # Force exact xy to avoid fp roundtrip issues.
+ return event
+
def __str__(self):
return (f"{self.name}: "
f"xy=({self.x}, {self.y}) xydata=({self.xdata}, {self.ydata}) "
@@ -1503,6 +1522,22 @@ def __init__(self, name, canvas, key, x=0, y=0, guiEvent=None):
super().__init__(name, canvas, x, y, guiEvent=guiEvent)
self.key = key
+ @classmethod
+ def _from_ax_coords(cls, name, ax, xy, key, *args, **kwargs):
+ """
+ Generate a synthetic event at a given axes coordinate.
+
+ This method is intended for creating events during testing. The event
+ can be emitted by calling its ``_process()`` method.
+ """
+ # Separate from MouseEvent._from_ax_coords instead of being defined in the base
+ # class, due to different parameter order in the constructor signature.
+ x, y = ax.transData.transform(xy)
+ event = cls(name, ax.figure.canvas, key, x, y, *args, **kwargs)
+ event.inaxes = ax
+ event.xdata, event.ydata = xy # Force exact xy to avoid fp roundtrip issues.
+ return event
+
# Default callback for key events.
def _key_handler(event):
@@ -1619,7 +1654,8 @@ def _allow_interrupt(prepare_notifier, handle_sigint):
If SIGINT was indeed caught, after exiting the on_signal() function the
interpreter reacts to the signal according to the handler function which
had been set up by a signal.signal() call; here, we arrange to call the
- backend-specific *handle_sigint* function. Finally, we call the old SIGINT
+ backend-specific *handle_sigint* function, passing the notifier object
+ as returned by prepare_notifier(). Finally, we call the old SIGINT
handler with the same arguments that were given to our custom handler.
We do this only if the old handler for SIGINT was not None, which means
@@ -1629,7 +1665,7 @@ def _allow_interrupt(prepare_notifier, handle_sigint):
Parameters
----------
prepare_notifier : Callable[[socket.socket], object]
- handle_sigint : Callable[[], object]
+ handle_sigint : Callable[[object], object]
"""
old_sigint_handler = signal.getsignal(signal.SIGINT)
@@ -1645,9 +1681,10 @@ def _allow_interrupt(prepare_notifier, handle_sigint):
notifier = prepare_notifier(rsock)
def save_args_and_handle_sigint(*args):
- nonlocal handler_args
+ nonlocal handler_args, notifier
handler_args = args
- handle_sigint()
+ handle_sigint(notifier)
+ notifier = None
signal.signal(signal.SIGINT, save_args_and_handle_sigint)
try:
@@ -2761,10 +2798,6 @@ class _Mode(str, Enum):
def __str__(self):
return self.value
- @property
- def _navigate_mode(self):
- return self.name if self is not _Mode.NONE else None
-
class NavigationToolbar2:
"""
@@ -3035,8 +3068,6 @@ def pan(self, *args):
else:
self.mode = _Mode.PAN
self.canvas.widgetlock(self)
- for a in self.canvas.figure.get_axes():
- a.set_navigate_mode(self.mode._navigate_mode)
_PanInfo = namedtuple("_PanInfo", "button axes cid")
@@ -3097,8 +3128,6 @@ def zoom(self, *args):
else:
self.mode = _Mode.ZOOM
self.canvas.widgetlock(self)
- for a in self.canvas.figure.get_axes():
- a.set_navigate_mode(self.mode._navigate_mode)
_ZoomInfo = namedtuple("_ZoomInfo", "button start_xy axes cid cbar")
diff --git a/lib/matplotlib/backend_bases.pyi b/lib/matplotlib/backend_bases.pyi
index 24669bfb3aeb..c65d39415472 100644
--- a/lib/matplotlib/backend_bases.pyi
+++ b/lib/matplotlib/backend_bases.pyi
@@ -21,7 +21,18 @@ from matplotlib.transforms import Bbox, BboxBase, Transform, TransformedPath
from collections.abc import Callable, Iterable, Sequence
from typing import Any, IO, Literal, NamedTuple, TypeVar, overload
from numpy.typing import ArrayLike
-from .typing import ColorType, LineStyleType, CapStyleType, JoinStyleType
+from .typing import (
+ CapStyleType,
+ CloseEventType,
+ ColorType,
+ DrawEventType,
+ JoinStyleType,
+ KeyEventType,
+ LineStyleType,
+ MouseEventType,
+ PickEventType,
+ ResizeEventType,
+)
def register_backend(
format: str, backend: str | type[FigureCanvasBase], description: str | None = ...
@@ -354,37 +365,28 @@ class FigureCanvasBase:
@overload
def mpl_connect(
self,
- s: Literal[
- "button_press_event",
- "motion_notify_event",
- "scroll_event",
- "figure_enter_event",
- "figure_leave_event",
- "axes_enter_event",
- "axes_leave_event",
- "button_release_event",
- ],
+ s: MouseEventType,
func: Callable[[MouseEvent], Any],
) -> int: ...
@overload
def mpl_connect(
self,
- s: Literal["key_press_event", "key_release_event"],
+ s: KeyEventType,
func: Callable[[KeyEvent], Any],
) -> int: ...
@overload
- def mpl_connect(self, s: Literal["pick_event"], func: Callable[[PickEvent], Any]) -> int: ...
+ def mpl_connect(self, s: PickEventType, func: Callable[[PickEvent], Any]) -> int: ...
@overload
- def mpl_connect(self, s: Literal["resize_event"], func: Callable[[ResizeEvent], Any]) -> int: ...
+ def mpl_connect(self, s: ResizeEventType, func: Callable[[ResizeEvent], Any]) -> int: ...
@overload
- def mpl_connect(self, s: Literal["close_event"], func: Callable[[CloseEvent], Any]) -> int: ...
+ def mpl_connect(self, s: CloseEventType, func: Callable[[CloseEvent], Any]) -> int: ...
@overload
- def mpl_connect(self, s: str, func: Callable[[Event], Any]) -> int: ...
+ def mpl_connect(self, s: DrawEventType, func: Callable[[DrawEvent], Any]) -> int: ...
def mpl_disconnect(self, cid: int) -> None: ...
def new_timer(
self,
diff --git a/lib/matplotlib/backend_tools.py b/lib/matplotlib/backend_tools.py
index 9410a73eff5f..0c03bd0800f4 100644
--- a/lib/matplotlib/backend_tools.py
+++ b/lib/matplotlib/backend_tools.py
@@ -668,9 +668,6 @@ def disable(self, event=None):
def trigger(self, sender, event, data=None):
self.toolmanager.get_tool(_views_positions).add_figure(self.figure)
super().trigger(sender, event, data)
- new_navigate_mode = self.name.upper() if self.toggled else None
- for ax in self.figure.axes:
- ax.set_navigate_mode(new_navigate_mode)
def scroll_zoom(self, event):
# https://gist.github.com/tacaswell/3144287
diff --git a/lib/matplotlib/backends/_backend_tk.py b/lib/matplotlib/backends/_backend_tk.py
index 0bbff1379ffa..813e0c60620f 100644
--- a/lib/matplotlib/backends/_backend_tk.py
+++ b/lib/matplotlib/backends/_backend_tk.py
@@ -22,8 +22,36 @@
TimerBase, ToolContainerBase, cursors, _Mode, MouseButton,
CloseEvent, KeyEvent, LocationEvent, MouseEvent, ResizeEvent)
from matplotlib._pylab_helpers import Gcf
-from . import _tkagg
-from ._tkagg import TK_PHOTO_COMPOSITE_OVERLAY, TK_PHOTO_COMPOSITE_SET
+
+try:
+ from . import _tkagg
+ from ._tkagg import TK_PHOTO_COMPOSITE_OVERLAY, TK_PHOTO_COMPOSITE_SET
+except ImportError as e:
+ # catch incompatibility of python-build-standalone with Tk
+ cause1 = getattr(e, '__cause__', None)
+ cause2 = getattr(cause1, '__cause__', None)
+ if (isinstance(cause1, ImportError) and
+ isinstance(cause2, AttributeError) and
+ "'_tkinter' has no attribute '__file__'" in str(cause2)):
+
+ is_uv_python = "/uv/python" in (os.path.realpath(sys.executable))
+ if is_uv_python:
+ raise ImportError(
+ "Failed to import tkagg backend. You appear to be using an outdated "
+ "version of uv's managed Python distribution which is not compatible "
+ "with Tk. Please upgrade to the latest uv version, then update "
+ "Python with: `uv python upgrade --reinstall`"
+ ) from e
+ else:
+ raise ImportError(
+ "Failed to import tkagg backend. This is likely caused by using a "
+ "Python executable based on python-build-standalone, which is not "
+ "compatible with Tk. Recent versions of python-build-standalone "
+ "should be compatible with Tk. Please update your python version "
+ "or select another backend."
+ ) from e
+ else:
+ raise
_log = logging.getLogger(__name__)
@@ -775,7 +803,7 @@ def _recolor_icon(image, color):
image_data = np.asarray(image).copy()
black_mask = (image_data[..., :3] == 0).all(axis=-1)
image_data[black_mask, :3] = color
- return Image.fromarray(image_data, mode="RGBA")
+ return Image.fromarray(image_data)
# Use the high-resolution (48x48 px) icon if it exists and is needed
with Image.open(path_large if (size > 24 and path_large.exists())
diff --git a/lib/matplotlib/backends/backend_agg.py b/lib/matplotlib/backends/backend_agg.py
index b435ae565ce4..33b0be18ca2d 100644
--- a/lib/matplotlib/backends/backend_agg.py
+++ b/lib/matplotlib/backends/backend_agg.py
@@ -25,6 +25,7 @@
from math import radians, cos, sin
import numpy as np
+from PIL import features
import matplotlib as mpl
from matplotlib import _api, cbook
@@ -510,7 +511,19 @@ def print_tif(self, filename_or_obj, *, metadata=None, pil_kwargs=None):
def print_webp(self, filename_or_obj, *, metadata=None, pil_kwargs=None):
self._print_pil(filename_or_obj, "webp", pil_kwargs, metadata)
- print_gif.__doc__, print_jpg.__doc__, print_tif.__doc__, print_webp.__doc__ = map(
+ def print_avif(self, filename_or_obj, *, metadata=None, pil_kwargs=None):
+ if not features.check("avif"):
+ raise RuntimeError(
+ "The installed pillow version does not support avif. Full "
+ "avif support has been added in pillow 11.3."
+ )
+ self._print_pil(filename_or_obj, "avif", pil_kwargs, metadata)
+
+ (print_gif.__doc__,
+ print_jpg.__doc__,
+ print_tif.__doc__,
+ print_webp.__doc__,
+ print_avif.__doc__) = map(
"""
Write the figure to a {} file.
@@ -518,10 +531,13 @@ def print_webp(self, filename_or_obj, *, metadata=None, pil_kwargs=None):
----------
filename_or_obj : str or path-like or file-like
The file to write to.
+ metadata : None
+ Unused for pillow-based writers. All supported options
+ can be passed via *pil_kwargs*.
pil_kwargs : dict, optional
Additional keyword arguments that are passed to
`PIL.Image.Image.save` when saving the figure.
- """.format, ["GIF", "JPEG", "TIFF", "WebP"])
+ """.format, ["GIF", "JPEG", "TIFF", "WebP", "AVIF"])
@_Backend.export
diff --git a/lib/matplotlib/backends/backend_gtk4.py b/lib/matplotlib/backends/backend_gtk4.py
index 620c9e5b94b6..cd38968779ed 100644
--- a/lib/matplotlib/backends/backend_gtk4.py
+++ b/lib/matplotlib/backends/backend_gtk4.py
@@ -30,6 +30,7 @@
)
_GOBJECT_GE_3_47 = gi.version_info >= (3, 47, 0)
+_GTK_GE_4_12 = Gtk.check_version(4, 12, 0) is None
class FigureCanvasGTK4(_FigureCanvasGTK, Gtk.DrawingArea):
@@ -48,7 +49,10 @@ def __init__(self, figure=None):
self.set_draw_func(self._draw_func)
self.connect('resize', self.resize_event)
- self.connect('notify::scale-factor', self._update_device_pixel_ratio)
+ if _GTK_GE_4_12:
+ self.connect('realize', self._realize_event)
+ else:
+ self.connect('notify::scale-factor', self._update_device_pixel_ratio)
click = Gtk.GestureClick()
click.set_button(0) # All buttons.
@@ -237,10 +241,20 @@ def _get_key(self, keyval, keycode, state):
and not (mod == "shift" and unikey.isprintable()))]
return "+".join([*mods, key])
+ def _realize_event(self, obj):
+ surface = self.get_native().get_surface()
+ surface.connect('notify::scale', self._update_device_pixel_ratio)
+ self._update_device_pixel_ratio()
+
def _update_device_pixel_ratio(self, *args, **kwargs):
# We need to be careful in cases with mixed resolution displays if
# device_pixel_ratio changes.
- if self._set_device_pixel_ratio(self.get_scale_factor()):
+ if _GTK_GE_4_12:
+ scale = self.get_native().get_surface().get_scale()
+ else:
+ scale = self.get_scale_factor()
+ assert scale is not None
+ if self._set_device_pixel_ratio(scale):
self.draw()
def _draw_rubberband(self, rect):
diff --git a/lib/matplotlib/backends/backend_pdf.py b/lib/matplotlib/backends/backend_pdf.py
index 6d6bea585ff3..d63808eb3925 100644
--- a/lib/matplotlib/backends/backend_pdf.py
+++ b/lib/matplotlib/backends/backend_pdf.py
@@ -721,7 +721,7 @@ def __init__(self, filename, metadata=None):
self._internal_font_seq = (Name(f'F{i}') for i in itertools.count(1))
self._fontNames = {} # maps filenames to internal font names
- self._dviFontInfo = {} # maps dvi font names to embedding information
+ self._dviFontInfo = {} # maps pdf names to dvifonts
self._character_tracker = _backend_pdf_ps.CharacterTracker()
self.alphaStates = {} # maps alpha values to graphics state objects
@@ -764,9 +764,30 @@ def __init__(self, filename, metadata=None):
self.writeObject(self.resourceObject, resources)
fontNames = _api.deprecated("3.11")(property(lambda self: self._fontNames))
- dviFontInfo = _api.deprecated("3.11")(property(lambda self: self._dviFontInfo))
type1Descriptors = _api.deprecated("3.11")(property(lambda _: {}))
+ @_api.deprecated("3.11")
+ @property
+ def dviFontInfo(self):
+ d = {}
+ tex_font_map = dviread.PsfontsMap(dviread.find_tex_file('pdftex.map'))
+ for pdfname, dvifont in self._dviFontInfo.items():
+ psfont = tex_font_map[dvifont.texname]
+ if psfont.filename is None:
+ raise ValueError(
+ "No usable font file found for {} (TeX: {}); "
+ "the font may lack a Type-1 version"
+ .format(psfont.psname, dvifont.texname))
+ d[dvifont.texname] = types.SimpleNamespace(
+ dvifont=dvifont,
+ pdfname=pdfname,
+ fontfile=psfont.filename,
+ basefont=psfont.psname,
+ encodingfile=psfont.encoding,
+ effects=psfont.effects,
+ )
+ return d
+
def newPage(self, width, height):
self.endStream()
@@ -938,39 +959,19 @@ def fontName(self, fontprop):
def dviFontName(self, dvifont):
"""
Given a dvi font object, return a name suitable for Op.selectfont.
- This registers the font information internally (in ``_dviFontInfo``) if
- not yet registered.
- """
-
- dvi_info = self._dviFontInfo.get(dvifont.texname)
- if dvi_info is not None:
- return dvi_info.pdfname
-
- tex_font_map = dviread.PsfontsMap(dviread.find_tex_file('pdftex.map'))
- psfont = tex_font_map[dvifont.texname]
- if psfont.filename is None:
- raise ValueError(
- "No usable font file found for {} (TeX: {}); "
- "the font may lack a Type-1 version"
- .format(psfont.psname, dvifont.texname))
- pdfname = next(self._internal_font_seq)
+ Register the font internally (in ``_dviFontInfo``) if not yet registered.
+ """
+ pdfname = Name(f"F-{dvifont.texname.decode('ascii')}")
_log.debug('Assigning font %s = %s (dvi)', pdfname, dvifont.texname)
- self._dviFontInfo[dvifont.texname] = types.SimpleNamespace(
- dvifont=dvifont,
- pdfname=pdfname,
- fontfile=psfont.filename,
- basefont=psfont.psname,
- encodingfile=psfont.encoding,
- effects=psfont.effects)
- return pdfname
+ self._dviFontInfo[pdfname] = dvifont
+ return Name(pdfname)
def writeFonts(self):
fonts = {}
- for dviname, info in sorted(self._dviFontInfo.items()):
- Fx = info.pdfname
- _log.debug('Embedding Type-1 font %s from dvi.', dviname)
- fonts[Fx] = self._embedTeXFont(info)
+ for pdfname, dvifont in sorted(self._dviFontInfo.items()):
+ _log.debug('Embedding Type-1 font %s from dvi.', dvifont.texname)
+ fonts[pdfname] = self._embedTeXFont(dvifont)
for filename in sorted(self._fontNames):
Fx = self._fontNames[filename]
_log.debug('Embedding font %s.', filename)
@@ -998,9 +999,14 @@ def _write_afm_font(self, filename):
self.writeObject(fontdictObject, fontdict)
return fontdictObject
- def _embedTeXFont(self, fontinfo):
- _log.debug('Embedding TeX font %s - fontinfo=%s',
- fontinfo.dvifont.texname, fontinfo.__dict__)
+ def _embedTeXFont(self, dvifont):
+ tex_font_map = dviread.PsfontsMap(dviread.find_tex_file('pdftex.map'))
+ psfont = tex_font_map[dvifont.texname]
+ if psfont.filename is None:
+ raise ValueError(
+ "No usable font file found for {} (TeX: {}); "
+ "the font may lack a Type-1 version"
+ .format(psfont.psname, dvifont.texname))
# The font dictionary is the top-level object describing a font
fontdictObject = self.reserveObject('font dictionary')
@@ -1010,17 +1016,17 @@ def _embedTeXFont(self, fontinfo):
}
# Read the font file and apply any encoding changes and effects
- t1font = _type1font.Type1Font(fontinfo.fontfile)
- if fontinfo.encodingfile is not None:
+ t1font = _type1font.Type1Font(psfont.filename)
+ if psfont.encoding is not None:
t1font = t1font.with_encoding(
- {i: c for i, c in enumerate(dviread._parse_enc(fontinfo.encodingfile))}
+ {i: c for i, c in enumerate(dviread._parse_enc(psfont.encoding))}
)
- if fontinfo.effects:
- t1font = t1font.transform(fontinfo.effects)
+ if psfont.effects:
+ t1font = t1font.transform(psfont.effects)
# Reduce the font to only the glyphs used in the document, get the encoding
# for that subset, and compute various properties based on the encoding.
- chars = frozenset(self._character_tracker.used[fontinfo.dvifont.fname])
+ chars = frozenset(self._character_tracker.used[dvifont.fname])
t1font = t1font.subset(chars, self._get_subset_prefix(chars))
fontdict['BaseFont'] = Name(t1font.prop['FontName'])
# createType1Descriptor writes the font data as a side effect
@@ -1029,19 +1035,16 @@ def _embedTeXFont(self, fontinfo):
fontdict['Encoding'] = self._generate_encoding(encoding)
fc = fontdict['FirstChar'] = min(encoding.keys(), default=0)
lc = fontdict['LastChar'] = max(encoding.keys(), default=255)
-
# Convert glyph widths from TeX 12.20 fixed point to 1/1000 text space units
- tfm = fontinfo.dvifont._tfm
- widths = [(1000 * metrics.tex_width) >> 20
- if (metrics := tfm.get_metrics(char)) else 0
+ font_metrics = dvifont._metrics
+ widths = [(1000 * glyph_metrics.tex_width) >> 20
+ if (glyph_metrics := font_metrics.get_metrics(char)) else 0
for char in range(fc, lc + 1)]
fontdict['Widths'] = widthsObject = self.reserveObject('glyph widths')
self.writeObject(widthsObject, widths)
-
self.writeObject(fontdictObject, fontdict)
return fontdictObject
-
def _generate_encoding(self, encoding):
prev = -2
result = []
@@ -1055,7 +1058,6 @@ def _generate_encoding(self, encoding):
'Differences': result
}
-
@_api.delete_parameter("3.11", "fontfile")
def createType1Descriptor(self, t1font, fontfile=None):
# Create and write the font descriptor and the font file
@@ -1781,7 +1783,7 @@ def _writeImg(self, data, id, smask=None):
data[:, :, 2])
indices = np.argsort(palette24).astype(np.uint8)
rgb8 = indices[np.searchsorted(palette24, rgb24, sorter=indices)]
- img = Image.fromarray(rgb8, mode='P')
+ img = Image.fromarray(rgb8).convert("P")
img.putpalette(palette)
png_data, bit_depth, palette = self._writePng(img)
if bit_depth is None or palette is None:
diff --git a/lib/matplotlib/backends/backend_ps.py b/lib/matplotlib/backends/backend_ps.py
index f6b8455a15a7..ea5868387918 100644
--- a/lib/matplotlib/backends/backend_ps.py
+++ b/lib/matplotlib/backends/backend_ps.py
@@ -406,7 +406,7 @@ class RendererPS(_backend_pdf_ps.RendererPDFPSBase):
def __init__(self, width, height, pswriter, imagedpi=72):
# Although postscript itself is dpi independent, we need to inform the
# image code about a requested dpi to generate high resolution images
- # and them scale them before embedding them.
+ # and then scale them before embedding them.
super().__init__(width, height)
self._pswriter = pswriter
if mpl.rcParams['text.usetex']:
diff --git a/lib/matplotlib/backends/backend_qt.py b/lib/matplotlib/backends/backend_qt.py
index 5cde4866cad7..d0aded5fff63 100644
--- a/lib/matplotlib/backends/backend_qt.py
+++ b/lib/matplotlib/backends/backend_qt.py
@@ -169,9 +169,14 @@ def _may_clear_sock():
# be forgiving about reading an empty socket.
pass
- return sn # Actually keep the notifier alive.
-
- def handle_sigint():
+ # We return the QSocketNotifier so that the caller holds a reference, and we
+ # also explicitly clean it up in handle_sigint(). Without doing both, deletion
+ # of the socket notifier can happen prematurely or not at all.
+ return sn
+
+ def handle_sigint(sn):
+ sn.deleteLater()
+ QtCore.QCoreApplication.sendPostedEvents(sn, QtCore.QEvent.Type.DeferredDelete)
if hasattr(qapp_or_eventloop, 'closeAllWindows'):
qapp_or_eventloop.closeAllWindows()
qapp_or_eventloop.quit()
@@ -257,12 +262,22 @@ def _update_screen(self, screen):
screen.physicalDotsPerInchChanged.connect(self._update_pixel_ratio)
screen.logicalDotsPerInchChanged.connect(self._update_pixel_ratio)
+ def eventFilter(self, source, event):
+ if event.type() == QtCore.QEvent.Type.DevicePixelRatioChange:
+ self._update_pixel_ratio()
+ return super().eventFilter(source, event)
+
def showEvent(self, event):
# Set up correct pixel ratio, and connect to any signal changes for it,
# once the window is shown (and thus has these attributes).
window = self.window().windowHandle()
- window.screenChanged.connect(self._update_screen)
- self._update_screen(window.screen())
+ current_version = tuple(int(x) for x in QtCore.qVersion().split('.', 2)[:2])
+ if current_version >= (6, 6):
+ self._update_pixel_ratio()
+ window.installEventFilter(self)
+ else:
+ window.screenChanged.connect(self._update_screen)
+ self._update_screen(window.screen())
def set_cursor(self, cursor):
# docstring inherited
@@ -502,7 +517,7 @@ def _draw_idle(self):
if not self._draw_pending:
return
self._draw_pending = False
- if self.height() <= 0 or self.width() <= 0:
+ if _isdeleted(self) or self.height() <= 0 or self.width() <= 0:
return
try:
self.draw()
diff --git a/lib/matplotlib/backends/backend_webagg_core.py b/lib/matplotlib/backends/backend_webagg_core.py
index 33490ff66dc5..4afe088db8d1 100644
--- a/lib/matplotlib/backends/backend_webagg_core.py
+++ b/lib/matplotlib/backends/backend_webagg_core.py
@@ -178,6 +178,9 @@ def __init__(self, *args, **kwargs):
# Track mouse events to fill in the x, y position of key events.
self._last_mouse_xy = (None, None)
+ # Control whether scroll events prevent default browser behavior
+ self._capture_scroll = False
+
def show(self):
# show the figure window
from matplotlib.pyplot import show
@@ -224,6 +227,28 @@ def set_image_mode(self, mode):
self._current_image_mode = mode
self.handle_send_image_mode(None)
+ def set_capture_scroll(self, capture):
+ """
+ Set whether the scroll events on the canvas will scroll the page.
+
+ Parameters
+ ----------
+ capture : bool
+ """
+ if self._capture_scroll != capture:
+ self._capture_scroll = capture
+ self.send_event("capture_scroll", capture_scroll=capture)
+
+ def get_capture_scroll(self):
+ """
+ Get whether scroll events are currently captured by the canvas.
+
+ Returns
+ -------
+ bool
+ """
+ return self._capture_scroll
+
def get_diff_image(self):
if self._png_is_old:
renderer = self.get_renderer()
@@ -335,6 +360,8 @@ def handle_refresh(self, event):
# Normal toolbar init would refresh this, but it happens before the
# browser canvas is set up.
self.toolbar.set_history_buttons()
+ # Send the current capture_scroll state to newly connected clients
+ self.send_event('capture_scroll', capture_scroll=self._capture_scroll)
self.draw_idle()
def handle_resize(self, event):
diff --git a/lib/matplotlib/backends/web_backend/js/mpl.js b/lib/matplotlib/backends/web_backend/js/mpl.js
index 303260773a2f..f2bfc43bd0e4 100644
--- a/lib/matplotlib/backends/web_backend/js/mpl.js
+++ b/lib/matplotlib/backends/web_backend/js/mpl.js
@@ -24,6 +24,8 @@ mpl.figure = function (figure_id, websocket, ondownload, parent_element) {
this.supports_binary = this.ws.binaryType !== undefined;
+ this.capture_scroll = false;
+
if (!this.supports_binary) {
var warnings = document.getElementById('mpl-warnings');
if (warnings) {
@@ -313,6 +315,9 @@ mpl.figure.prototype._init_canvas = function () {
} else {
event.step = -1;
}
+ if (fig.capture_scroll) {
+ event.preventDefault();
+ }
on_mouse_event_closure('scroll')(event);
});
@@ -524,6 +529,10 @@ mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {
}
};
+mpl.figure.prototype.handle_capture_scroll = function (fig, msg) {
+ fig.capture_scroll = msg['capture_scroll'];
+};
+
mpl.figure.prototype.updated_canvas_event = function () {
// Called whenever the canvas gets updated.
this.send_message('ack', {});
diff --git a/lib/matplotlib/backends/web_backend/nbagg_uat.ipynb b/lib/matplotlib/backends/web_backend/nbagg_uat.ipynb
index e9fc62bc2883..0513fee2b54c 100644
--- a/lib/matplotlib/backends/web_backend/nbagg_uat.ipynb
+++ b/lib/matplotlib/backends/web_backend/nbagg_uat.ipynb
@@ -309,7 +309,7 @@
"metadata": {},
"outputs": [],
"source": [
- "from matplotlib.backends.backend_nbagg import new_figure_manager,show\n",
+ "from matplotlib.backends.backend_nbagg import new_figure_manager\n",
"\n",
"manager = new_figure_manager(1000)\n",
"fig = manager.canvas.figure\n",
@@ -341,15 +341,18 @@
"x = np.arange(0, 2*np.pi, 0.01) # x-array\n",
"line, = ax.plot(x, np.sin(x))\n",
"\n",
+ "\n",
"def animate(i):\n",
" line.set_ydata(np.sin(x+i/10.0)) # update the data\n",
" return line,\n",
"\n",
- "#Init only required for blitting to give a clean slate.\n",
+ "\n",
+ "# Init only required for blitting to give a clean slate.\n",
"def init():\n",
" line.set_ydata(np.ma.array(x, mask=True))\n",
" return line,\n",
"\n",
+ "\n",
"ani = animation.FuncAnimation(fig, animate, np.arange(1, 200), init_func=init,\n",
" interval=100., blit=True)\n",
"plt.show()"
@@ -405,6 +408,8 @@
"ln, = ax.plot(x,y)\n",
"evt = []\n",
"colors = iter(itertools.cycle(['r', 'g', 'b', 'k', 'c']))\n",
+ "\n",
+ "\n",
"def on_event(event):\n",
" if event.name.startswith('key'):\n",
" fig.suptitle('%s: %s' % (event.name, event.key))\n",
@@ -417,6 +422,7 @@
" fig.canvas.draw()\n",
" fig.canvas.draw_idle()\n",
"\n",
+ "\n",
"fig.canvas.mpl_connect('button_press_event', on_event)\n",
"fig.canvas.mpl_connect('button_release_event', on_event)\n",
"fig.canvas.mpl_connect('scroll_event', on_event)\n",
@@ -448,10 +454,12 @@
"fig, ax = plt.subplots()\n",
"text = ax.text(0.5, 0.5, '', ha='center')\n",
"\n",
+ "\n",
"def update(text):\n",
" text.set(text=time.ctime())\n",
" text.axes.figure.canvas.draw()\n",
- " \n",
+ "\n",
+ "\n",
"timer = fig.canvas.new_timer(500, [(update, [text], {})])\n",
"timer.start()\n",
"plt.show()"
@@ -471,7 +479,7 @@
"outputs": [],
"source": [
"fig, ax = plt.subplots()\n",
- "text = ax.text(0.5, 0.5, '', ha='center') \n",
+ "text = ax.text(0.5, 0.5, '', ha='center')\n",
"timer = fig.canvas.new_timer(500, [(update, [text], {})])\n",
"\n",
"timer.single_shot = True\n",
@@ -578,11 +586,12 @@
"cnt = itertools.count()\n",
"bg = None\n",
"\n",
+ "\n",
"def onclick_handle(event):\n",
" \"\"\"Should draw elevating green line on each mouse click\"\"\"\n",
" global bg\n",
" if bg is None:\n",
- " bg = ax.figure.canvas.copy_from_bbox(ax.bbox) \n",
+ " bg = ax.figure.canvas.copy_from_bbox(ax.bbox)\n",
" ax.figure.canvas.restore_region(bg)\n",
"\n",
" cur_y = (next(cnt) % 10) * 0.1\n",
@@ -590,6 +599,7 @@
" ax.draw_artist(ln)\n",
" ax.figure.canvas.blit(ax.bbox)\n",
"\n",
+ "\n",
"fig, ax = plt.subplots()\n",
"ax.plot([0, 1], [0, 1], 'r')\n",
"ln, = ax.plot([0, 1], [0, 0], 'g', animated=True)\n",
@@ -598,13 +608,6 @@
"\n",
"ax.figure.canvas.mpl_connect('button_press_event', onclick_handle)"
]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": []
}
],
"metadata": {
diff --git a/lib/matplotlib/cbook.py b/lib/matplotlib/cbook.py
index 3100cc4da81d..8b438e7d79b5 100644
--- a/lib/matplotlib/cbook.py
+++ b/lib/matplotlib/cbook.py
@@ -43,16 +43,20 @@ class _ExceptionInfo:
users and result in incorrect tracebacks.
"""
- def __init__(self, cls, *args):
+ def __init__(self, cls, *args, notes=None):
self._cls = cls
self._args = args
+ self._notes = notes if notes is not None else []
@classmethod
def from_exception(cls, exc):
- return cls(type(exc), *exc.args)
+ return cls(type(exc), *exc.args, notes=getattr(exc, "__notes__", []))
def to_exception(self):
- return self._cls(*self._args)
+ exc = self._cls(*self._args)
+ for note in self._notes:
+ exc.add_note(note)
+ return exc
def _get_running_interactive_framework():
@@ -2228,6 +2232,9 @@ def _g_sig_digits(value, delta):
Return the number of significant digits to %g-format *value*, assuming that
it is known with an error of *delta*.
"""
+ # For inf or nan, the precision doesn't matter.
+ if not math.isfinite(value):
+ return 0
if delta == 0:
if value == 0:
# if both value and delta are 0, np.spacing below returns 5e-324
@@ -2241,11 +2248,10 @@ def _g_sig_digits(value, delta):
# digits before the decimal point (floor(log10(45.67)) + 1 = 2): the total
# is 4 significant digits. A value of 0 contributes 1 "digit" before the
# decimal point.
- # For inf or nan, the precision doesn't matter.
return max(
0,
(math.floor(math.log10(abs(value))) + 1 if value else 1)
- - math.floor(math.log10(delta))) if math.isfinite(value) else 0
+ - math.floor(math.log10(delta)))
def _unikey_or_keysym_to_mplkey(unikey, keysym):
diff --git a/lib/matplotlib/cbook.pyi b/lib/matplotlib/cbook.pyi
index 6c2d9c303eb2..ad14841463e8 100644
--- a/lib/matplotlib/cbook.pyi
+++ b/lib/matplotlib/cbook.pyi
@@ -14,10 +14,10 @@ from typing import (
Generic,
IO,
Literal,
- Sequence,
TypeVar,
overload,
)
+from collections.abc import Sequence
_T = TypeVar("_T")
diff --git a/lib/matplotlib/cm.py b/lib/matplotlib/cm.py
index ef5bf0719d3b..299059177a20 100644
--- a/lib/matplotlib/cm.py
+++ b/lib/matplotlib/cm.py
@@ -92,10 +92,8 @@ def __init__(self, cmaps):
self._builtin_cmaps = tuple(cmaps)
def __getitem__(self, item):
- try:
- return self._cmaps[item].copy()
- except KeyError:
- raise KeyError(f"{item!r} is not a known colormap name") from None
+ cmap = _api.check_getitem(self._cmaps, colormap=item, _error_cls=KeyError)
+ return cmap.copy()
def __iter__(self):
return iter(self._cmaps)
diff --git a/lib/matplotlib/cm.pyi b/lib/matplotlib/cm.pyi
index 366b336fe04d..128d1a563286 100644
--- a/lib/matplotlib/cm.pyi
+++ b/lib/matplotlib/cm.pyi
@@ -20,3 +20,185 @@ _multivar_colormaps: ColormapRegistry = ...
_bivar_colormaps: ColormapRegistry = ...
ScalarMappable = _ScalarMappable
+
+magma: colors.Colormap
+inferno: colors.Colormap
+plasma: colors.Colormap
+viridis: colors.Colormap
+cividis: colors.Colormap
+twilight: colors.Colormap
+twilight_shifted: colors.Colormap
+turbo: colors.Colormap
+berlin: colors.Colormap
+managua: colors.Colormap
+vanimo: colors.Colormap
+Blues: colors.Colormap
+BrBG: colors.Colormap
+BuGn: colors.Colormap
+BuPu: colors.Colormap
+CMRmap: colors.Colormap
+GnBu: colors.Colormap
+Greens: colors.Colormap
+Greys: colors.Colormap
+OrRd: colors.Colormap
+Oranges: colors.Colormap
+PRGn: colors.Colormap
+PiYG: colors.Colormap
+PuBu: colors.Colormap
+PuBuGn: colors.Colormap
+PuOr: colors.Colormap
+PuRd: colors.Colormap
+Purples: colors.Colormap
+RdBu: colors.Colormap
+RdGy: colors.Colormap
+RdPu: colors.Colormap
+RdYlBu: colors.Colormap
+RdYlGn: colors.Colormap
+Reds: colors.Colormap
+Spectral: colors.Colormap
+Wistia: colors.Colormap
+YlGn: colors.Colormap
+YlGnBu: colors.Colormap
+YlOrBr: colors.Colormap
+YlOrRd: colors.Colormap
+afmhot: colors.Colormap
+autumn: colors.Colormap
+binary: colors.Colormap
+bone: colors.Colormap
+brg: colors.Colormap
+bwr: colors.Colormap
+cool: colors.Colormap
+coolwarm: colors.Colormap
+copper: colors.Colormap
+cubehelix: colors.Colormap
+flag: colors.Colormap
+gist_earth: colors.Colormap
+gist_gray: colors.Colormap
+gist_heat: colors.Colormap
+gist_ncar: colors.Colormap
+gist_rainbow: colors.Colormap
+gist_stern: colors.Colormap
+gist_yarg: colors.Colormap
+gnuplot: colors.Colormap
+gnuplot2: colors.Colormap
+gray: colors.Colormap
+hot: colors.Colormap
+hsv: colors.Colormap
+jet: colors.Colormap
+nipy_spectral: colors.Colormap
+ocean: colors.Colormap
+pink: colors.Colormap
+prism: colors.Colormap
+rainbow: colors.Colormap
+seismic: colors.Colormap
+spring: colors.Colormap
+summer: colors.Colormap
+terrain: colors.Colormap
+winter: colors.Colormap
+Accent: colors.Colormap
+Dark2: colors.Colormap
+Paired: colors.Colormap
+Pastel1: colors.Colormap
+Pastel2: colors.Colormap
+Set1: colors.Colormap
+Set2: colors.Colormap
+Set3: colors.Colormap
+tab10: colors.Colormap
+tab20: colors.Colormap
+tab20b: colors.Colormap
+tab20c: colors.Colormap
+grey: colors.Colormap
+gist_grey: colors.Colormap
+gist_yerg: colors.Colormap
+Grays: colors.Colormap
+# Reversed colormaps
+magma_r: colors.Colormap
+inferno_r: colors.Colormap
+plasma_r: colors.Colormap
+viridis_r: colors.Colormap
+cividis_r: colors.Colormap
+twilight_r: colors.Colormap
+twilight_shifted_r: colors.Colormap
+turbo_r: colors.Colormap
+berlin_r: colors.Colormap
+managua_r: colors.Colormap
+vanimo_r: colors.Colormap
+Blues_r: colors.Colormap
+BrBG_r: colors.Colormap
+BuGn_r: colors.Colormap
+BuPu_r: colors.Colormap
+CMRmap_r: colors.Colormap
+GnBu_r: colors.Colormap
+Greens_r: colors.Colormap
+Greys_r: colors.Colormap
+OrRd_r: colors.Colormap
+Oranges_r: colors.Colormap
+PRGn_r: colors.Colormap
+PiYG_r: colors.Colormap
+PuBu_r: colors.Colormap
+PuBuGn_r: colors.Colormap
+PuOr_r: colors.Colormap
+PuRd_r: colors.Colormap
+Purples_r: colors.Colormap
+RdBu_r: colors.Colormap
+RdGy_r: colors.Colormap
+RdPu_r: colors.Colormap
+RdYlBu_r: colors.Colormap
+RdYlGn_r: colors.Colormap
+Reds_r: colors.Colormap
+Spectral_r: colors.Colormap
+Wistia_r: colors.Colormap
+YlGn_r: colors.Colormap
+YlGnBu_r: colors.Colormap
+YlOrBr_r: colors.Colormap
+YlOrRd_r: colors.Colormap
+afmhot_r: colors.Colormap
+autumn_r: colors.Colormap
+binary_r: colors.Colormap
+bone_r: colors.Colormap
+brg_r: colors.Colormap
+bwr_r: colors.Colormap
+cool_r: colors.Colormap
+coolwarm_r: colors.Colormap
+copper_r: colors.Colormap
+cubehelix_r: colors.Colormap
+flag_r: colors.Colormap
+gist_earth_r: colors.Colormap
+gist_gray_r: colors.Colormap
+gist_heat_r: colors.Colormap
+gist_ncar_r: colors.Colormap
+gist_rainbow_r: colors.Colormap
+gist_stern_r: colors.Colormap
+gist_yarg_r: colors.Colormap
+gnuplot_r: colors.Colormap
+gnuplot2_r: colors.Colormap
+gray_r: colors.Colormap
+hot_r: colors.Colormap
+hsv_r: colors.Colormap
+jet_r: colors.Colormap
+nipy_spectral_r: colors.Colormap
+ocean_r: colors.Colormap
+pink_r: colors.Colormap
+prism_r: colors.Colormap
+rainbow_r: colors.Colormap
+seismic_r: colors.Colormap
+spring_r: colors.Colormap
+summer_r: colors.Colormap
+terrain_r: colors.Colormap
+winter_r: colors.Colormap
+Accent_r: colors.Colormap
+Dark2_r: colors.Colormap
+Paired_r: colors.Colormap
+Pastel1_r: colors.Colormap
+Pastel2_r: colors.Colormap
+Set1_r: colors.Colormap
+Set2_r: colors.Colormap
+Set3_r: colors.Colormap
+tab10_r: colors.Colormap
+tab20_r: colors.Colormap
+tab20b_r: colors.Colormap
+tab20c_r: colors.Colormap
+grey_r: colors.Colormap
+gist_grey_r: colors.Colormap
+gist_yerg_r: colors.Colormap
+Grays_r: colors.Colormap
diff --git a/lib/matplotlib/collections.py b/lib/matplotlib/collections.py
index ec6d40805da0..684e15cdf854 100644
--- a/lib/matplotlib/collections.py
+++ b/lib/matplotlib/collections.py
@@ -283,7 +283,7 @@ def get_datalim(self, transData):
offsets = self.get_offsets()
- if any(transform.contains_branch_seperately(transData)):
+ if any(transform.contains_branch_separately(transData)):
# collections that are just in data units (like quiver)
# can properly have the axes limits set by their shape +
# offset. LineCollections that have no offsets can
diff --git a/lib/matplotlib/colorbar.py b/lib/matplotlib/colorbar.py
index 19bdbe605d88..2a11477ed1c2 100644
--- a/lib/matplotlib/colorbar.py
+++ b/lib/matplotlib/colorbar.py
@@ -373,7 +373,7 @@ def __init__(
colors=[mpl.rcParams['axes.edgecolor']],
linewidths=[0.5 * mpl.rcParams['axes.linewidth']],
clip_on=False)
- self.ax.add_collection(self.dividers)
+ self.ax.add_collection(self.dividers, autolim=False)
self._locator = None
self._minorlocator = None
@@ -807,7 +807,7 @@ def add_lines(self, *args, **kwargs):
xy = self.ax.transAxes.inverted().transform(inches.transform(xy))
col.set_clip_path(mpath.Path(xy, closed=True),
self.ax.transAxes)
- self.ax.add_collection(col)
+ self.ax.add_collection(col, autolim=False)
self.stale = True
def update_ticks(self):
@@ -1455,8 +1455,7 @@ def make_axes(parents, location=None, orientation=None, fraction=0.15,
cax = fig.add_axes(pbcb, label="")
for a in parents:
- # tell the parent it has a colorbar
- a._colorbars += [cax]
+ a._colorbars.append(cax) # tell the parent it has a colorbar
cax._colorbar_info = dict(
parents=parents,
location=location,
@@ -1549,6 +1548,7 @@ def make_axes_gridspec(parent, *, location=None, orientation=None,
fig = parent.get_figure()
cax = fig.add_subplot(ss_cb, label="")
+ parent._colorbars.append(cax) # tell the parent it has a colorbar
cax.set_anchor(anchor)
cax.set_box_aspect(aspect)
cax.set_aspect('auto')
diff --git a/lib/matplotlib/colorizer.py b/lib/matplotlib/colorizer.py
index b4223f389804..a94790979078 100644
--- a/lib/matplotlib/colorizer.py
+++ b/lib/matplotlib/colorizer.py
@@ -90,7 +90,7 @@ def norm(self):
@norm.setter
def norm(self, norm):
- _api.check_isinstance((colors.Normalize, str, None), norm=norm)
+ _api.check_isinstance((colors.Norm, str, None), norm=norm)
if norm is None:
norm = colors.Normalize()
elif isinstance(norm, str):
@@ -275,9 +275,9 @@ def set_clim(self, vmin=None, vmax=None):
# until both vmin and vmax are updated
with self.norm.callbacks.blocked(signal='changed'):
if vmin is not None:
- self.norm.vmin = colors._sanitize_extrema(vmin)
+ self.norm.vmin = vmin
if vmax is not None:
- self.norm.vmax = colors._sanitize_extrema(vmax)
+ self.norm.vmax = vmax
# emit a update signal if the limits are changed
if orig_vmin_vmax != (self.norm.vmin, self.norm.vmax):
diff --git a/lib/matplotlib/colorizer.pyi b/lib/matplotlib/colorizer.pyi
index f35ebe5295e4..9a5a73415d83 100644
--- a/lib/matplotlib/colorizer.pyi
+++ b/lib/matplotlib/colorizer.pyi
@@ -10,12 +10,12 @@ class Colorizer:
def __init__(
self,
cmap: str | colors.Colormap | None = ...,
- norm: str | colors.Normalize | None = ...,
+ norm: str | colors.Norm | None = ...,
) -> None: ...
@property
- def norm(self) -> colors.Normalize: ...
+ def norm(self) -> colors.Norm: ...
@norm.setter
- def norm(self, norm: colors.Normalize | str | None) -> None: ...
+ def norm(self, norm: colors.Norm | str | None) -> None: ...
def to_rgba(
self,
x: np.ndarray,
@@ -63,10 +63,10 @@ class _ColorizerInterface:
def get_cmap(self) -> colors.Colormap: ...
def set_cmap(self, cmap: str | colors.Colormap) -> None: ...
@property
- def norm(self) -> colors.Normalize: ...
+ def norm(self) -> colors.Norm: ...
@norm.setter
- def norm(self, norm: colors.Normalize | str | None) -> None: ...
- def set_norm(self, norm: colors.Normalize | str | None) -> None: ...
+ def norm(self, norm: colors.Norm | str | None) -> None: ...
+ def set_norm(self, norm: colors.Norm | str | None) -> None: ...
def autoscale(self) -> None: ...
def autoscale_None(self) -> None: ...
@@ -74,7 +74,7 @@ class _ColorizerInterface:
class _ScalarMappable(_ColorizerInterface):
def __init__(
self,
- norm: colors.Normalize | None = ...,
+ norm: colors.Norm | None = ...,
cmap: str | colors.Colormap | None = ...,
*,
colorizer: Colorizer | None = ...,
diff --git a/lib/matplotlib/colors.py b/lib/matplotlib/colors.py
index dd5d22130904..f60c8eb48134 100644
--- a/lib/matplotlib/colors.py
+++ b/lib/matplotlib/colors.py
@@ -41,6 +41,7 @@
import base64
from collections.abc import Sequence, Mapping
+from abc import ABC, abstractmethod
import functools
import importlib
import inspect
@@ -949,7 +950,7 @@ def with_alpha(self, alpha):
if not isinstance(alpha, Real):
raise TypeError(f"'alpha' must be numeric or None, not {type(alpha)}")
if not 0 <= alpha <= 1:
- ValueError("'alpha' must be between 0 and 1, inclusive")
+ raise ValueError("'alpha' must be between 0 and 1, inclusive")
new_cm = self.copy()
if not new_cm._isinit:
new_cm._init()
@@ -2257,7 +2258,98 @@ def _init(self):
self._isinit = True
-class Normalize:
+class Norm(ABC):
+ """
+ Abstract base class for normalizations.
+
+ Subclasses include `Normalize` which maps from a scalar to
+ a scalar. However, this class makes no such requirement, and subclasses may
+ support the normalization of multiple variates simultaneously, with
+ separate normalization for each variate.
+ """
+
+ def __init__(self):
+ self.callbacks = cbook.CallbackRegistry(signals=["changed"])
+
+ @property
+ @abstractmethod
+ def vmin(self):
+ """Lower limit of the input data interval; maps to 0."""
+ pass
+
+ @property
+ @abstractmethod
+ def vmax(self):
+ """Upper limit of the input data interval; maps to 1."""
+ pass
+
+ @property
+ @abstractmethod
+ def clip(self):
+ """
+ Determines the behavior for mapping values outside the range ``[vmin, vmax]``.
+
+ See the *clip* parameter in `.Normalize`.
+ """
+ pass
+
+ @abstractmethod
+ def __call__(self, value, clip=None):
+ """
+ Normalize the data and return the normalized data.
+
+ Parameters
+ ----------
+ value
+ Data to normalize.
+ clip : bool, optional
+ See the description of the parameter *clip* in `.Normalize`.
+
+ If ``None``, defaults to ``self.clip`` (which defaults to
+ ``False``).
+
+ Notes
+ -----
+ If not already initialized, ``self.vmin`` and ``self.vmax`` are
+ initialized using ``self.autoscale_None(value)``.
+ """
+ pass
+
+ @abstractmethod
+ def autoscale(self, A):
+ """Set *vmin*, *vmax* to min, max of *A*."""
+ pass
+
+ @abstractmethod
+ def autoscale_None(self, A):
+ """If *vmin* or *vmax* are not set, use the min/max of *A* to set them."""
+ pass
+
+ @abstractmethod
+ def scaled(self):
+ """Return whether *vmin* and *vmax* are both set."""
+ pass
+
+ def _changed(self):
+ """
+ Call this whenever the norm is changed to notify all the
+ callback listeners to the 'changed' signal.
+ """
+ self.callbacks.process('changed')
+
+ @property
+ @abstractmethod
+ def n_components(self):
+ """
+ The number of normalized components.
+
+ This is the number of elements of the parameter to ``__call__`` and of
+ *vmin*, *vmax*.
+ """
+ pass
+
+
+class Normalize(Norm):
"""
A class which, when called, maps values within the interval
``[vmin, vmax]`` linearly to the interval ``[0.0, 1.0]``. The mapping of
@@ -2307,15 +2399,15 @@ def __init__(self, vmin=None, vmax=None, clip=False):
-----
If ``vmin == vmax``, input data will be mapped to 0.
"""
+ super().__init__()
self._vmin = _sanitize_extrema(vmin)
self._vmax = _sanitize_extrema(vmax)
self._clip = clip
self._scale = None
- self.callbacks = cbook.CallbackRegistry(signals=["changed"])
@property
def vmin(self):
- """Lower limit of the input data interval; maps to 0."""
+ # docstring inherited
return self._vmin
@vmin.setter
@@ -2327,7 +2419,7 @@ def vmin(self, value):
@property
def vmax(self):
- """Upper limit of the input data interval; maps to 1."""
+ # docstring inherited
return self._vmax
@vmax.setter
@@ -2339,11 +2431,7 @@ def vmax(self, value):
@property
def clip(self):
- """
- Determines the behavior for mapping values outside the range ``[vmin, vmax]``.
-
- See the *clip* parameter in `.Normalize`.
- """
+ # docstring inherited
return self._clip
@clip.setter
@@ -2352,13 +2440,6 @@ def clip(self, value):
self._clip = value
self._changed()
- def _changed(self):
- """
- Call this whenever the norm is changed to notify all the
- callback listeners to the 'changed' signal.
- """
- self.callbacks.process('changed')
-
@staticmethod
def process_value(value):
"""
@@ -2400,24 +2481,7 @@ def process_value(value):
return result, is_scalar
def __call__(self, value, clip=None):
- """
- Normalize the data and return the normalized data.
-
- Parameters
- ----------
- value
- Data to normalize.
- clip : bool, optional
- See the description of the parameter *clip* in `.Normalize`.
-
- If ``None``, defaults to ``self.clip`` (which defaults to
- ``False``).
-
- Notes
- -----
- If not already initialized, ``self.vmin`` and ``self.vmax`` are
- initialized using ``self.autoscale_None(value)``.
- """
+ # docstring inherited
if clip is None:
clip = self.clip
@@ -2468,7 +2532,7 @@ def inverse(self, value):
return vmin + value * (vmax - vmin)
def autoscale(self, A):
- """Set *vmin*, *vmax* to min, max of *A*."""
+ # docstring inherited
with self.callbacks.blocked():
# Pause callbacks while we are updating so we only get
# a single update signal at the end
@@ -2477,7 +2541,7 @@ def autoscale(self, A):
self._changed()
def autoscale_None(self, A):
- """If *vmin* or *vmax* are not set, use the min/max of *A* to set them."""
+ # docstring inherited
A = np.asanyarray(A)
if isinstance(A, np.ma.MaskedArray):
@@ -2491,9 +2555,22 @@ def autoscale_None(self, A):
self.vmax = A.max()
def scaled(self):
- """Return whether *vmin* and *vmax* are both set."""
+ # docstring inherited
return self.vmin is not None and self.vmax is not None
+ @property
+ def n_components(self):
+ """
+ The number of distinct components supported (1).
+
+ This is the number of elements of the parameter to ``__call__`` and of
+ *vmin*, *vmax*.
+
+ This class support only a single component, as opposed to `MultiNorm`
+ which supports multiple components.
+ """
+ return 1
+
class TwoSlopeNorm(Normalize):
def __init__(self, vcenter, vmin=None, vmax=None):
@@ -2775,7 +2852,7 @@ def _make_norm_from_scale(
unlike to arbitrary lambdas.
"""
- class Norm(base_norm_cls):
+ class ScaleNorm(base_norm_cls):
def __reduce__(self):
cls = type(self)
# If the class is toplevel-accessible, it is possible to directly
@@ -2855,15 +2932,15 @@ def autoscale_None(self, A):
return super().autoscale_None(in_trf_domain)
if base_norm_cls is Normalize:
- Norm.__name__ = f"{scale_cls.__name__}Norm"
- Norm.__qualname__ = f"{scale_cls.__qualname__}Norm"
+ ScaleNorm.__name__ = f"{scale_cls.__name__}Norm"
+ ScaleNorm.__qualname__ = f"{scale_cls.__qualname__}Norm"
else:
- Norm.__name__ = base_norm_cls.__name__
- Norm.__qualname__ = base_norm_cls.__qualname__
- Norm.__module__ = base_norm_cls.__module__
- Norm.__doc__ = base_norm_cls.__doc__
+ ScaleNorm.__name__ = base_norm_cls.__name__
+ ScaleNorm.__qualname__ = base_norm_cls.__qualname__
+ ScaleNorm.__module__ = base_norm_cls.__module__
+ ScaleNorm.__doc__ = base_norm_cls.__doc__
- return Norm
+ return ScaleNorm
def _create_empty_object_of_class(cls):
@@ -3219,6 +3296,300 @@ def inverse(self, value):
return value
+class MultiNorm(Norm):
+ """
+ A class which contains multiple scalar norms.
+ """
+
+ def __init__(self, norms, vmin=None, vmax=None, clip=None):
+ """
+ Parameters
+ ----------
+ norms : list of (str or `Normalize`)
+ The constituent norms. The list must have a minimum length of 1.
+ vmin, vmax : None or list of (float or None)
+ Limits of the constituent norms.
+ If a list, one value is assigned to each of the constituent
+ norms.
+ If None, the limits of the constituent norms
+ are not changed.
+ clip : None or list of bools, default: None
+ Determines the behavior for mapping values outside the range
+ ``[vmin, vmax]`` for the constituent norms.
+ If a list, each value is assigned to each of the constituent
+ norms.
+ If None, the behaviour of the constituent norms is not changed.
+ """
+ if cbook.is_scalar_or_string(norms):
+ raise ValueError(
+ "MultiNorm must be assigned an iterable of norms, where each "
+ f"norm is of type `str`, or `Normalize`, not {type(norms)}")
+
+ if len(norms) < 1:
+ raise ValueError("MultiNorm must be assigned at least one norm")
+
+ def resolve(norm):
+ if isinstance(norm, str):
+ scale_cls = _api.check_getitem(scale._scale_mapping, norm=norm)
+ return mpl.colorizer._auto_norm_from_scale(scale_cls)()
+ elif isinstance(norm, Normalize):
+ return norm
+ else:
+ raise ValueError(
+ "Each norm assigned to MultiNorm must be "
+ f"of type `str`, or `Normalize`, not {type(norm)}")
+
+ self._norms = tuple(resolve(norm) for norm in norms)
+
+ self.callbacks = cbook.CallbackRegistry(signals=["changed"])
+
+ self.vmin = vmin
+ self.vmax = vmax
+ self.clip = clip
+
+ for n in self._norms:
+ n.callbacks.connect('changed', self._changed)
+
+ @property
+ def n_components(self):
+ """Number of norms held by this `MultiNorm`."""
+ return len(self._norms)
+
+ @property
+ def norms(self):
+ """The individual norms held by this `MultiNorm`."""
+ return self._norms
+
+ @property
+ def vmin(self):
+ """The lower limit of each constituent norm."""
+ return tuple(n.vmin for n in self._norms)
+
+ @vmin.setter
+ def vmin(self, values):
+ if values is None:
+ return
+ if not np.iterable(values) or len(values) != self.n_components:
+ raise ValueError("*vmin* must have one component for each norm. "
+ f"Expected an iterable of length {self.n_components}, "
+ f"but got {values!r}")
+ with self.callbacks.blocked():
+ for norm, v in zip(self.norms, values):
+ norm.vmin = v
+ self._changed()
+
+ @property
+ def vmax(self):
+ """The upper limit of each constituent norm."""
+ return tuple(n.vmax for n in self._norms)
+
+ @vmax.setter
+ def vmax(self, values):
+ if values is None:
+ return
+ if not np.iterable(values) or len(values) != self.n_components:
+ raise ValueError("*vmax* must have one component for each norm. "
+ f"Expected an iterable of length {self.n_components}, "
+ f"but got {values!r}")
+ with self.callbacks.blocked():
+ for norm, v in zip(self.norms, values):
+ norm.vmax = v
+ self._changed()
+
+ @property
+ def clip(self):
+ """The clip behaviour of each constituent norm."""
+ return tuple(n.clip for n in self._norms)
+
+ @clip.setter
+ def clip(self, values):
+ if values is None:
+ return
+ if not np.iterable(values) or len(values) != self.n_components:
+ raise ValueError("*clip* must have one component for each norm. "
+ f"Expected an iterable of length {self.n_components}, "
+ f"but got {values!r}")
+ with self.callbacks.blocked():
+ for norm, v in zip(self.norms, values):
+ norm.clip = v
+ self._changed()
+
+ def _changed(self):
+ """
+ Call this whenever the norm is changed to notify all the
+ callback listeners to the 'changed' signal.
+ """
+ self.callbacks.process('changed')
+
+ def __call__(self, values, clip=None):
+ """
+ Normalize the data and return the normalized data.
+
+ Each component of the input is normalized via the constituent norm.
+
+ Parameters
+ ----------
+ values : array-like
+ The input data, as an iterable or a structured numpy array.
+
+ - If iterable, must be of length `n_components`. Each element can be a
+ scalar or array-like and is normalized through the corresponding norm.
+ - If structured array, must have `n_components` fields. Each field
+ is normalized through the corresponding norm.
+
+ clip : list of bools or None, optional
+ Determines the behavior for mapping values outside the range
+ ``[vmin, vmax]``. See the description of the parameter *clip* in
+ `.Normalize`.
+ If ``None``, defaults to ``self.clip`` (which defaults to
+ ``False``).
+
+ Returns
+ -------
+ tuple
+ Normalized input values
+
+ Notes
+ -----
+ If not already initialized, ``self.vmin`` and ``self.vmax`` are
+ initialized using ``self.autoscale_None(values)``.
+ """
+ if clip is None:
+ clip = self.clip
+ if not np.iterable(clip) or len(clip) != self.n_components:
+ raise ValueError("*clip* must have one component for each norm. "
+ f"Expected an iterable of length {self.n_components}, "
+ f"but got {clip!r}")
+
+ values = self._iterable_components_in_data(values, self.n_components)
+ result = tuple(n(v, clip=c) for n, v, c in zip(self.norms, values, clip))
+ return result
+
+ def inverse(self, values):
+ """
+ Map the normalized values (i.e., index in the colormap) back to data values.
+
+ Parameters
+ ----------
+ values : array-like
+ The input data, as an iterable or a structured numpy array.
+
+ - If iterable, must be of length `n_components`. Each element can be a
+ scalar or array-like and is mapped through the corresponding norm.
+ - If structured array, must have `n_components` fields. Each field
+ is mapped through the the corresponding norm.
+
+ """
+ values = self._iterable_components_in_data(values, self.n_components)
+ result = tuple(n.inverse(v) for n, v in zip(self.norms, values))
+ return result
+
+ def autoscale(self, A):
+ """
+ For each constituent norm, set *vmin*, *vmax* to min, max of the corresponding
+ component in *A*.
+
+ Parameters
+ ----------
+ A : array-like
+ The input data, as an iterable or a structured numpy array.
+
+ - If iterable, must be of length `n_components`. Each element
+ is used for the limits of one constituent norm.
+ - If structured array, must have `n_components` fields. Each field
+ is used for the limits of one constituent norm.
+ """
+ with self.callbacks.blocked():
+ A = self._iterable_components_in_data(A, self.n_components)
+ for n, a in zip(self.norms, A):
+ n.autoscale(a)
+ self._changed()
+
+ def autoscale_None(self, A):
+ """
+ If *vmin* or *vmax* are not set on any constituent norm,
+ use the min/max of the corresponding component in *A* to set them.
+
+ Parameters
+ ----------
+ A : array-like
+ The input data, as an iterable or a structured numpy array.
+
+ - If iterable, must be of length `n_components`. Each element
+ is used for the limits of one constituent norm.
+ - If structured array, must have `n_components` fields. Each field
+ is used for the limits of one constituent norm.
+ """
+ with self.callbacks.blocked():
+ A = self._iterable_components_in_data(A, self.n_components)
+ for n, a in zip(self.norms, A):
+ n.autoscale_None(a)
+ self._changed()
+
+ def scaled(self):
+ """Return whether both *vmin* and *vmax* are set on all constituent norms."""
+ return all(n.scaled() for n in self.norms)
+
+ @staticmethod
+ def _iterable_components_in_data(data, n_components):
+ """
+ Provides an iterable over the components contained in the data.
+
+ An input array with `n_components` fields is returned as a tuple of length n
+ referencing slices of the original array.
+
+ Parameters
+ ----------
+ data : array-like
+ The input data, as an iterable or a structured numpy array.
+
+ - If iterable, must be of length `n_components`
+ - If structured array, must have `n_components` fields.
+
+ Returns
+ -------
+ tuple of np.ndarray
+
+ """
+ if isinstance(data, np.ndarray) and data.dtype.fields is not None:
+ # structured array
+ if len(data.dtype.fields) != n_components:
+ raise ValueError(
+ "Structured array inputs to MultiNorm must have the same "
+ "number of fields as components in the MultiNorm. Expected "
+ f"{n_components}, but got {len(data.dtype.fields)} fields"
+ )
+ else:
+ return tuple(data[field] for field in data.dtype.names)
+ try:
+ n_elements = len(data)
+ except TypeError:
+ raise ValueError("MultiNorm expects a sequence with one element per "
+ f"component as input, but got {data!r} instead")
+ if n_elements != n_components:
+ if isinstance(data, np.ndarray) and data.shape[-1] == n_components:
+ if len(data.shape) == 2:
+ raise ValueError(
+ f"MultiNorm expects a sequence with one element per component. "
+ "You can use `data_transposed = data.T` "
+ "to convert the input data of shape "
+ f"{data.shape} to a compatible shape {data.shape[::-1]}")
+ else:
+ raise ValueError(
+ f"MultiNorm expects a sequence with one element per component. "
+ "You can use `data_as_list = [data[..., i] for i in "
+ "range(data.shape[-1])]` to convert the input data of shape "
+ f" {data.shape} to a compatible list")
+
+ raise ValueError(
+ "MultiNorm expects a sequence with one element per component. "
+ f"This MultiNorm has {n_components} components, but got a sequence "
+ f"with {n_elements} elements"
+ )
+
+ return tuple(data[i] for i in range(n_elements))
+
+
def rgb_to_hsv(arr):
"""
Convert an array of float RGB values (in the range [0, 1]) to HSV values.
diff --git a/lib/matplotlib/colors.pyi b/lib/matplotlib/colors.pyi
index eadd759bcaa3..07bf01b8f995 100644
--- a/lib/matplotlib/colors.pyi
+++ b/lib/matplotlib/colors.pyi
@@ -1,4 +1,5 @@
from collections.abc import Callable, Iterable, Iterator, Mapping, Sequence
+from abc import ABC, abstractmethod
from matplotlib import cbook, scale
import re
@@ -249,8 +250,32 @@ class BivarColormapFromImage(BivarColormap):
origin: Sequence[float] = ..., name: str = ...
) -> None: ...
-class Normalize:
+class Norm(ABC):
callbacks: cbook.CallbackRegistry
+ def __init__(self) -> None: ...
+ @property
+ @abstractmethod
+ def vmin(self) -> float | tuple[float] | None: ...
+ @property
+ @abstractmethod
+ def vmax(self) -> float | tuple[float] | None: ...
+ @property
+ @abstractmethod
+ def clip(self) -> bool | tuple[bool]: ...
+ @abstractmethod
+ def __call__(self, value: np.ndarray, clip: bool | None = ...) -> ArrayLike: ...
+ @abstractmethod
+ def autoscale(self, A: ArrayLike) -> None: ...
+ @abstractmethod
+ def autoscale_None(self, A: ArrayLike) -> None: ...
+ @abstractmethod
+ def scaled(self) -> bool: ...
+ @abstractmethod
+ @property
+ def n_components(self) -> int: ...
+
+
+class Normalize(Norm):
def __init__(
self, vmin: float | None = ..., vmax: float | None = ..., clip: bool = ...
) -> None: ...
@@ -283,6 +308,8 @@ class Normalize:
def autoscale(self, A: ArrayLike) -> None: ...
def autoscale_None(self, A: ArrayLike) -> None: ...
def scaled(self) -> bool: ...
+ @property
+ def n_components(self) -> Literal[1]: ...
class TwoSlopeNorm(Normalize):
def __init__(
@@ -387,6 +414,44 @@ class BoundaryNorm(Normalize):
class NoNorm(Normalize): ...
+class MultiNorm(Norm):
+ # Here "type: ignore[override]" is used for functions with a return type
+ # that differs from the function in the base class.
+ # i.e. where `MultiNorm` returns a tuple and Normalize returns a `float` etc.
+ def __init__(
+ self,
+ norms: ArrayLike,
+ vmin: ArrayLike | None = ...,
+ vmax: ArrayLike | None = ...,
+ clip: ArrayLike | None = ...
+ ) -> None: ...
+ @property
+ def norms(self) -> tuple[Normalize, ...]: ...
+ @property # type: ignore[override]
+ def vmin(self) -> tuple[float | None, ...]: ...
+ @vmin.setter
+ def vmin(self, values: ArrayLike | None) -> None: ...
+ @property # type: ignore[override]
+ def vmax(self) -> tuple[float | None, ...]: ...
+ @vmax.setter
+ def vmax(self, valued: ArrayLike | None) -> None: ...
+ @property # type: ignore[override]
+ def clip(self) -> tuple[bool, ...]: ...
+ @clip.setter
+ def clip(self, values: ArrayLike | None) -> None: ...
+ @overload
+ def __call__(self, values: tuple[np.ndarray, ...], clip: ArrayLike | bool | None = ...) -> tuple[np.ndarray, ...]: ...
+ @overload
+ def __call__(self, values: tuple[float, ...], clip: ArrayLike | bool | None = ...) -> tuple[float, ...]: ...
+ @overload
+ def __call__(self, values: ArrayLike, clip: ArrayLike | bool | None = ...) -> tuple: ...
+ def inverse(self, values: ArrayLike) -> tuple: ... # type: ignore[override]
+ def autoscale(self, A: ArrayLike) -> None: ...
+ def autoscale_None(self, A: ArrayLike) -> None: ...
+ def scaled(self) -> bool: ...
+ @property
+ def n_components(self) -> int: ...
+
def rgb_to_hsv(arr: ArrayLike) -> np.ndarray: ...
def hsv_to_rgb(hsv: ArrayLike) -> np.ndarray: ...
diff --git a/lib/matplotlib/container.py b/lib/matplotlib/container.py
index b6dd43724f34..fcf2e6016db9 100644
--- a/lib/matplotlib/container.py
+++ b/lib/matplotlib/container.py
@@ -73,6 +73,48 @@ def __init__(self, patches, errorbar=None, *, datavalues=None,
self.orientation = orientation
super().__init__(patches, **kwargs)
+ @property
+ def bottoms(self):
+ """
+ Return the values at the lower end of the bars.
+
+ .. versionadded:: 3.11
+ """
+ if self.orientation == 'vertical':
+ return [p.get_y() for p in self.patches]
+ elif self.orientation == 'horizontal':
+ return [p.get_x() for p in self.patches]
+ else:
+ raise ValueError("orientation must be 'vertical' or 'horizontal'.")
+
+ @property
+ def tops(self):
+ """
+ Return the values at the upper end of the bars.
+
+ .. versionadded:: 3.11
+ """
+ if self.orientation == 'vertical':
+ return [p.get_y() + p.get_height() for p in self.patches]
+ elif self.orientation == 'horizontal':
+ return [p.get_x() + p.get_width() for p in self.patches]
+ else:
+ raise ValueError("orientation must be 'vertical' or 'horizontal'.")
+
+ @property
+ def position_centers(self):
+ """
+ Return the centers of bar positions.
+
+ .. versionadded:: 3.11
+ """
+ if self.orientation == 'vertical':
+ return [p.get_x() + p.get_width() / 2 for p in self.patches]
+ elif self.orientation == 'horizontal':
+ return [p.get_y() + p.get_height() / 2 for p in self.patches]
+ else:
+ raise ValueError("orientation must be 'vertical' or 'horizontal'.")
+
class ErrorbarContainer(Container):
"""
diff --git a/lib/matplotlib/container.pyi b/lib/matplotlib/container.pyi
index c66e7ba4b4c3..ff11830c544c 100644
--- a/lib/matplotlib/container.pyi
+++ b/lib/matplotlib/container.pyi
@@ -32,6 +32,12 @@ class BarContainer(Container):
orientation: Literal["vertical", "horizontal"] | None = ...,
**kwargs
) -> None: ...
+ @property
+ def bottoms(self) -> list[float]: ...
+ @property
+ def tops(self) -> list[float]: ...
+ @property
+ def position_centers(self) -> list[float]: ...
class ErrorbarContainer(Container):
lines: tuple[Line2D, tuple[Line2D, ...], tuple[LineCollection, ...]]
diff --git a/lib/matplotlib/contour.py b/lib/matplotlib/contour.py
index 8c5c9b566441..1125b9c5fbc6 100644
--- a/lib/matplotlib/contour.py
+++ b/lib/matplotlib/contour.py
@@ -76,7 +76,7 @@ def clabel(self, levels=None, *,
a subset of ``cs.levels``. If not given, all levels are labeled.
fontsize : str or float, default: :rc:`font.size`
- Size in points or relative size e.g., 'smaller', 'x-large'.
+ Size in points or relative size e.g., 'small', 'x-large'.
See `.Text.set_size` for accepted string values.
colors : :mpltype:`color` or colors or None, default: None
@@ -602,7 +602,7 @@ def __init__(self, ax, *args,
hatches=(None,), alpha=None, origin=None, extent=None,
cmap=None, colors=None, norm=None, vmin=None, vmax=None,
colorizer=None, extend='neither', antialiased=None, nchunk=0,
- locator=None, transform=None, negative_linestyles=None, clip_path=None,
+ locator=None, transform=None, negative_linestyles=None,
**kwargs):
"""
Draw contour lines or filled regions, depending on
@@ -656,7 +656,6 @@ def __init__(self, ax, *args,
super().__init__(
antialiaseds=antialiased,
alpha=alpha,
- clip_path=clip_path,
transform=transform,
colorizer=colorizer,
)
@@ -672,6 +671,9 @@ def __init__(self, ax, *args,
self.nchunk = nchunk
self.locator = locator
+ if "color" in kwargs:
+ raise _api.kwarg_error("ContourSet.__init__", "color")
+
if colorizer:
self._set_colorizer_check_keywords(colorizer, cmap=cmap,
norm=norm, vmin=vmin,
@@ -764,23 +766,18 @@ def __init__(self, ax, *args,
_api.warn_external('linewidths is ignored by contourf')
# Lower and upper contour levels.
lowers, uppers = self._get_lowers_and_uppers()
- self.set(
- edgecolor="none",
- # Default zorder taken from Collection
- zorder=kwargs.pop("zorder", 1),
- rasterized=kwargs.pop("rasterized", False),
- )
-
+ self.set(edgecolor="none")
else:
self.set(
facecolor="none",
linewidths=self._process_linewidths(linewidths),
linestyle=self._process_linestyles(linestyles),
+ label="_nolegend_",
# Default zorder taken from LineCollection, which is higher
# than for filled contours so that lines are displayed on top.
- zorder=kwargs.pop("zorder", 2),
- label="_nolegend_",
+ zorder=2,
)
+ self.set(**kwargs) # Let user-set values override defaults.
self.axes.add_collection(self, autolim=False)
self.sticky_edges.x[:] = [self._mins[0], self._maxs[0]]
@@ -790,12 +787,6 @@ def __init__(self, ax, *args,
self.changed() # set the colors
- if kwargs:
- _api.warn_external(
- 'The following kwargs were not used by contour: ' +
- ", ".join(map(repr, kwargs))
- )
-
allsegs = property(lambda self: [
[subp.vertices for subp in p._iter_connected_components()]
for p in self.get_paths()])
@@ -1342,7 +1333,7 @@ def _process_args(self, *args, corner_mask=None, algorithm=None, **kwargs):
# if the transform is not trans data, and some part of it
# contains transData, transform the xs and ys to data coordinates
if (t != self.axes.transData and
- any(t.contains_branch_seperately(self.axes.transData))):
+ any(t.contains_branch_separately(self.axes.transData))):
trans_to_data = t - self.axes.transData
pts = np.vstack([x.flat, y.flat]).T
transformed_pts = trans_to_data.transform(pts)
diff --git a/lib/matplotlib/dviread.py b/lib/matplotlib/dviread.py
index 9e8b6a5facf5..1a0f9219a498 100644
--- a/lib/matplotlib/dviread.py
+++ b/lib/matplotlib/dviread.py
@@ -26,12 +26,14 @@
import subprocess
import sys
from collections import namedtuple
-from functools import cache, lru_cache, partial, wraps
+from functools import cache, cached_property, lru_cache, partial, wraps
from pathlib import Path
+import fontTools.agl
import numpy as np
from matplotlib import _api, cbook, font_manager
+from matplotlib.ft2font import LoadFlags
_log = logging.getLogger(__name__)
@@ -67,42 +69,15 @@ class Text(namedtuple('Text', 'x y font glyph width')):
"""
A glyph in the dvi file.
- The *x* and *y* attributes directly position the glyph. The *font*,
- *glyph*, and *width* attributes are kept public for back-compatibility,
- but users wanting to draw the glyph themselves are encouraged to instead
- load the font specified by `font_path` at `font_size`, warp it with the
- effects specified by `font_effects`, and load the glyph at the FreeType
- glyph `index`.
- """
-
- def _get_pdftexmap_entry(self):
- return PsfontsMap(find_tex_file("pdftex.map"))[self.font.texname]
-
- @property
- def font_path(self):
- """The `~pathlib.Path` to the font for this glyph."""
- psfont = self._get_pdftexmap_entry()
- if psfont.filename is None:
- raise ValueError("No usable font file found for {} ({}); "
- "the font may lack a Type-1 version"
- .format(psfont.psname.decode("ascii"),
- psfont.texname.decode("ascii")))
- return Path(psfont.filename)
+ In order to render the glyph, load the glyph at index ``text.index``
+ from the font at ``text.font.resolve_path()`` with size ``text.font.size``,
+ warped with ``text.font.effects``, then draw it at position
+ ``(text.x, text.y)``.
- @property
- def font_size(self):
- """The font size."""
- return self.font.size
-
- @property
- def font_effects(self):
- """
- The "font effects" dict for this glyph.
-
- This dict contains the values for this glyph of SlantFont and
- ExtendFont (if any), read off :file:`pdftex.map`.
- """
- return self._get_pdftexmap_entry().effects
+ ``text.glyph`` is the glyph number actually stored in the dvi file (whose
+ interpretation depends on the font). ``text.width`` is the glyph width in
+ dvi units.
+ """
@property
def index(self):
@@ -112,25 +87,51 @@ def index(self):
# See DviFont._index_dvi_to_freetype for details on the index mapping.
return self.font._index_dvi_to_freetype(self.glyph)
- @property # To be deprecated together with font_size, font_effects.
+ font_path = property(lambda self: self.font.resolve_path())
+ font_size = property(lambda self: self.font.size)
+ font_effects = property(lambda self: self.font.effects)
+
+ @property # To be deprecated together with font_path, font_size, font_effects.
def glyph_name_or_index(self):
"""
- Either the glyph name or the native charmap glyph index.
-
- If :file:`pdftex.map` specifies an encoding for this glyph's font, that
- is a mapping of glyph indices to Adobe glyph names; use it to convert
- dvi indices to glyph names. Callers can then convert glyph names to
- glyph indices (with FT_Get_Name_Index/get_name_index), and load the
- glyph using FT_Load_Glyph/load_glyph.
-
- If :file:`pdftex.map` specifies no encoding, the indices directly map
- to the font's "native" charmap; glyphs should directly load using
- FT_Load_Char/load_char after selecting the native charmap.
+ The glyph name, the native charmap glyph index, or the raw glyph index.
+
+ If the font is a TrueType file (which can currently only happen for
+ DVI files generated by xetex or luatex), then this number is the raw
+ index of the glyph, which can be passed to FT_Load_Glyph/load_glyph.
+
+ Otherwise, the font is a PostScript font. For such fonts, if
+ :file:`pdftex.map` specifies an encoding for this glyph's font,
+ that is a mapping of glyph indices to Adobe glyph names; which
+ is used by this property to convert dvi numbers to glyph names.
+ Callers can then convert glyph names to glyph indices (with
+ FT_Get_Name_Index/get_name_index), and load the glyph using
+ FT_Load_Glyph/load_glyph.
+
+ If :file:`pdftex.map` specifies no encoding for a PostScript font,
+ this number is an index to the font's "native" charmap; glyphs should
+ directly load using FT_Load_Char/load_char after selecting the native
+ charmap.
"""
+ # The last section is only true on luatex since luaotfload 3.23; this
+ # must be checked by the code generated by texmanager. (luaotfload's
+ # docs states "No one should rely on the mapping between DVI character
+ # codes and font glyphs [prior to v3.15] unless they tightly
+ # control all involved versions and are deeply familiar with the
+ # implementation", but a further mapping bug was fixed in luaotfload
+ # commit 8f2dca4, first included in v3.23).
entry = self._get_pdftexmap_entry()
return (_parse_enc(entry.encoding)[self.glyph]
if entry.encoding is not None else self.glyph)
+ def _as_unicode_or_name(self):
+ if self.font.subfont:
+ raise NotImplementedError("Indexing TTC fonts is not supported yet")
+ face = font_manager.get_font(self.font.resolve_path())
+ glyph_name = face.get_glyph_name(self.index)
+ glyph_str = fontTools.agl.toUnicode(glyph_name)
+ return glyph_str or glyph_name
+
# Opcode argument parsing
#
@@ -408,7 +409,7 @@ def _put_char_real(self, char):
scale = font._scale
for x, y, f, g, w in font._vf[char].text:
newf = DviFont(scale=_mul1220(scale, f._scale),
- tfm=f._tfm, texname=f.texname, vf=f._vf)
+ metrics=f._metrics, texname=f.texname, vf=f._vf)
self.text.append(Text(self.h + _mul1220(x, scale),
self.v + _mul1220(y, scale),
newf, g, newf._width_of(g)))
@@ -504,10 +505,21 @@ def _fnt_def(self, k, c, s, d, a, l):
def _fnt_def_real(self, k, c, s, d, a, l):
n = self.file.read(a + l)
- fontname = n[-l:].decode('ascii')
+ fontname = n[-l:]
+ if fontname.startswith(b"[") and c == 0x4c756146: # c == "LuaF"
+ # See https://chat.stackexchange.com/rooms/106428 (and also
+ # https://tug.org/pipermail/dvipdfmx/2021-January/000168.html).
+ # AFAICT luatex's dvi drops info re: OpenType variation-axis values.
+ self.fonts[k] = DviFont.from_luatex(s, n)
+ return
+ fontname = fontname.decode("ascii")
try:
tfm = _tfmfile(fontname)
except FileNotFoundError as exc:
+ if fontname.startswith("[") and fontname.endswith(";") and c == 0:
+ exc.add_note(
+ "This dvi file was likely generated with a too-old "
+ "version of luaotfload; luaotfload 3.23 is required.")
# Explicitly allow defining missing fonts for Vf support; we only
# register an error when trying to load a glyph from a missing font
# and throw that error in Dvi._read. For Vf, _finalize_packet
@@ -521,12 +533,12 @@ def _fnt_def_real(self, k, c, s, d, a, l):
vf = _vffile(fontname)
except FileNotFoundError:
vf = None
- self.fonts[k] = DviFont(scale=s, tfm=tfm, texname=n, vf=vf)
+ self.fonts[k] = DviFont(scale=s, metrics=tfm, texname=n, vf=vf)
@_dispatch(247, state=_dvistate.pre, args=('u1', 'u4', 'u4', 'u4', 'u1'))
def _pre(self, i, num, den, mag, k):
self.file.read(k) # comment in the dvi file
- if i != 2:
+ if i not in [2, 7]: # 2: pdftex, luatex; 7: xetex
raise ValueError(f"Unknown dvi format {i}")
if num != 25400000 or den != 7227 * 2**16:
raise ValueError("Nonstandard units in dvi file")
@@ -547,13 +559,66 @@ def _post(self, _):
# TODO: actually read the postamble and finale?
# currently post_post just triggers closing the file
- @_dispatch(249)
- def _post_post(self, _):
+ @_dispatch(249, args=())
+ def _post_post(self):
+ raise NotImplementedError
+
+ @_dispatch(250, args=())
+ def _begin_reflect(self):
+ raise NotImplementedError
+
+ @_dispatch(251, args=())
+ def _end_reflect(self):
raise NotImplementedError
- @_dispatch(min=250, max=255)
- def _malformed(self, offset):
- raise ValueError(f"unknown command: byte {250 + offset}")
+ @_dispatch(252, args=())
+ def _define_native_font(self):
+ k = self._read_arg(4, signed=False)
+ s = self._read_arg(4, signed=False)
+ flags = self._read_arg(2, signed=False)
+ l = self._read_arg(1, signed=False)
+ n = self.file.read(l)
+ i = self._read_arg(4, signed=False)
+ effects = {}
+ if flags & 0x0200:
+ effects["rgba"] = [self._read_arg(1, signed=False) for _ in range(4)]
+ if flags & 0x1000:
+ effects["extend"] = self._read_arg(4, signed=True) / 65536
+ if flags & 0x2000:
+ effects["slant"] = self._read_arg(4, signed=True) / 65536
+ if flags & 0x4000:
+ effects["embolden"] = self._read_arg(4, signed=True) / 65536
+ self.fonts[k] = DviFont.from_xetex(s, n, i, effects)
+
+ @_dispatch(253, args=())
+ def _set_glyphs(self):
+ w = self._read_arg(4, signed=False)
+ k = self._read_arg(2, signed=False)
+ xy = [self._read_arg(4, signed=True) for _ in range(2 * k)]
+ g = [self._read_arg(2, signed=False) for _ in range(k)]
+ font = self.fonts[self.f]
+ for i in range(k):
+ self.text.append(Text(self.h + xy[2 * i], self.v + xy[2 * i + 1],
+ font, g[i], font._width_of(g[i])))
+ self.h += w
+
+ @_dispatch(254, args=())
+ def _set_text_and_glyphs(self):
+ l = self._read_arg(2, signed=False)
+ t = self.file.read(2 * l) # utf16
+ w = self._read_arg(4, signed=False)
+ k = self._read_arg(2, signed=False)
+ xy = [self._read_arg(4, signed=True) for _ in range(2 * k)]
+ g = [self._read_arg(2, signed=False) for _ in range(k)]
+ font = self.fonts[self.f]
+ for i in range(k):
+ self.text.append(Text(self.h + xy[2 * i], self.v + xy[2 * i + 1],
+ font, g[i], font._width_of(g[i])))
+ self.h += w
+
+ @_dispatch(255)
+ def _malformed(self, raw):
+ raise ValueError("unknown command: byte 255")
class DviFont:
@@ -571,10 +636,10 @@ class DviFont:
----------
scale : float
Factor by which the font is scaled from its natural size.
- tfm : Tfm
+ metrics : Tfm | TtfMetrics
TeX font metrics for this font
texname : bytes
- Name of the font as used internally by TeX and friends, as an ASCII
+ Name of the font as used internally in the DVI file, as an ASCII
bytestring. This is usually very different from any external font
names; `PsfontsMap` can be used to find the external name of the font.
vf : Vf
@@ -590,17 +655,54 @@ class DviFont:
Size of the font in Adobe points, converted from the slightly
smaller TeX points.
"""
- __slots__ = ('texname', 'size', '_scale', '_vf', '_tfm', '_encoding')
- def __init__(self, scale, tfm, texname, vf):
+ def __init__(self, scale, metrics, texname, vf):
_api.check_isinstance(bytes, texname=texname)
self._scale = scale
- self._tfm = tfm
+ self._metrics = metrics
self.texname = texname
self._vf = vf
- self.size = scale * (72.0 / (72.27 * 2**16))
+ self._path = None
self._encoding = None
+ @classmethod
+ def from_luatex(cls, scale, texname):
+ path_b, sep, rest = texname[1:].rpartition(b"]")
+ if not (texname.startswith(b"[") and sep and rest[:1] in [b"", b":"]):
+ raise ValueError(f"Invalid modern font name: {texname}")
+ # utf8 on Windows, not utf16!
+ path = path_b.decode("utf8") if os.name == "nt" else os.fsdecode(path_b)
+ subfont = 0
+ effects = {}
+ if rest[1:]:
+ for kv in rest[1:].decode("ascii").split(";"):
+ key, val = kv.split("=", 1)
+ if key == "index":
+ subfont = val
+ elif key in ["embolden", "slant", "extend"]:
+ effects[key] = int(val) / 65536
+ else:
+ _log.warning("Ignoring invalid key-value pair: %r", kv)
+ metrics = TtfMetrics(path)
+ font = cls(scale, metrics, texname, vf=None)
+ font._path = Path(path)
+ font.subfont = subfont
+ font.effects = effects
+ return font
+
+ @classmethod
+ def from_xetex(cls, scale, texname, subfont, effects):
+ # utf8 on Windows, not utf16!
+ path = texname.decode("utf8") if os.name == "nt" else os.fsdecode(texname)
+ metrics = TtfMetrics(path)
+ font = cls(scale, metrics, b"[" + texname + b"]", vf=None)
+ font._path = Path(path)
+ font.subfont = subfont
+ font.effects = effects
+ return font
+
+ size = property(lambda self: self._scale * (72.0 / (72.27 * 2**16)))
+
widths = _api.deprecated("3.11")(property(lambda self: [
(1000 * self._tfm.width.get(char, 0)) >> 20
for char in range(max(self._tfm.width, default=-1) + 1)]))
@@ -629,7 +731,7 @@ def __repr__(self):
def _width_of(self, char):
"""Width of char in dvi units."""
- metrics = self._tfm.get_metrics(char)
+ metrics = self._metrics.get_metrics(char)
if metrics is None:
_log.debug('No width for char %d in font %s.', char, self.texname)
return 0
@@ -637,7 +739,7 @@ def _width_of(self, char):
def _height_depth_of(self, char):
"""Height and depth of char in dvi units."""
- metrics = self._tfm.get_metrics(char)
+ metrics = self._metrics.get_metrics(char)
if metrics is None:
_log.debug('No metrics for char %d in font %s', char, self.texname)
return [0, 0]
@@ -654,26 +756,42 @@ def _height_depth_of(self, char):
hd[-1] = 0
return hd
+ def resolve_path(self):
+ if self._path is None:
+ psfont = PsfontsMap(find_tex_file("pdftex.map"))[self.texname]
+ if psfont.filename is None:
+ raise ValueError("No usable font file found for {} ({}); "
+ "the font may lack a Type-1 version"
+ .format(psfont.psname.decode("ascii"),
+ psfont.texname.decode("ascii")))
+ self._path = Path(psfont.filename)
+ return self._path
+
+ @cached_property
+ def subfont(self):
+ return 0
+
+ @cached_property
+ def effects(self):
+ return PsfontsMap(find_tex_file("pdftex.map"))[self.texname].effects
+
def _index_dvi_to_freetype(self, idx):
"""Convert dvi glyph indices to FreeType ones."""
# Glyphs indices stored in the dvi file map to FreeType glyph indices
# (i.e., which can be passed to FT_Load_Glyph) in various ways:
+ # - for xetex & luatex "native fonts", dvi indices are directly equal
+ # to FreeType indices.
# - if pdftex.map specifies an ".enc" file for the font, that file maps
# dvi indices to Adobe glyph names, which can then be converted to
# FreeType glyph indices with FT_Get_Name_Index.
# - if no ".enc" file is specified, then the font must be a Type 1
# font, and dvi indices directly index into the font's CharStrings
# vector.
- # - (xetex & luatex, currently unsupported, can also declare "native
- # fonts", for which dvi indices are equal to FreeType indices.)
+ if self.texname.startswith(b"["):
+ return idx
if self._encoding is None:
+ face = font_manager.get_font(self.resolve_path())
psfont = PsfontsMap(find_tex_file("pdftex.map"))[self.texname]
- if psfont.filename is None:
- raise ValueError("No usable font file found for {} ({}); "
- "the font may lack a Type-1 version"
- .format(psfont.psname.decode("ascii"),
- psfont.texname.decode("ascii")))
- face = font_manager.get_font(psfont.filename)
if psfont.encoding:
self._encoding = [face.get_name_index(name)
for name in _parse_enc(psfont.encoding)]
@@ -882,6 +1000,27 @@ def get_metrics(self, idx):
property(lambda self: {c: m.tex_depth for c, m in self._glyph_metrics}))
+class TtfMetrics:
+ def __init__(self, filename):
+ self._face = font_manager.get_font(filename, hinting_factor=1)
+
+ def get_metrics(self, idx):
+ # _mul1220 uses a truncating bitshift for compatibility with dvitype.
+ # When upem is 2048 the conversion to 12.20 is exact, but when
+ # upem is 1000 (e.g. lmroman10-regular.otf) the metrics themselves
+ # are not exactly representable as 12.20 fp. Manual testing via
+ # \sbox0{x}\count0=\wd0\typeout{\the\count0} suggests that metrics
+ # are rounded (not truncated) after conversion to 12.20 and before
+ # multiplication by the scale.
+ upem = self._face.units_per_EM # Usually 2048 or 1000.
+ g = self._face.load_glyph(idx, LoadFlags.NO_SCALE)
+ return TexMetrics(
+ tex_width=round(g.horiAdvance / upem * 2**20),
+ tex_height=round(g.horiBearingY / upem * 2**20),
+ tex_depth=round((g.height - g.horiBearingY) / upem * 2**20),
+ )
+
+
PsFont = namedtuple('PsFont', 'texname psname effects encoding filename')
@@ -1179,10 +1318,6 @@ def _fontfile(cls, suffix, texname):
import itertools
from argparse import ArgumentParser
- import fontTools.agl
-
- from matplotlib.ft2font import FT2Font
-
parser = ArgumentParser()
parser.add_argument("filename")
parser.add_argument("dpi", nargs="?", type=float, default=None)
@@ -1197,17 +1332,18 @@ def _print_fields(*args):
print(f"=== NEW PAGE === "
f"(w: {page.width}, h: {page.height}, d: {page.descent})")
print("--- GLYPHS ---")
- for font, group in itertools.groupby(
- page.text, lambda text: text.font):
- psfont = fontmap[font.texname]
- fontpath = psfont.filename
- print(f"font: {font.texname.decode('latin-1')} "
- f"(scale: {font._scale / 2 ** 20}) at {fontpath}")
- face = FT2Font(fontpath)
+ for font, group in itertools.groupby(page.text, lambda text: text.font):
+ font_name = (font.texname.decode("utf8") if os.name == "nt"
+ else os.fsdecode(font.texname))
+ if isinstance(font._metrics, Tfm):
+ print(f"font: {font_name} at {font.resolve_path()}")
+ else:
+ print(f"font: {font_name}")
+ print(f"scale: {font._scale / 2 ** 20}")
_print_fields("x", "y", "glyph", "chr", "w")
for text in group:
- glyph_str = fontTools.agl.toUnicode(face.get_glyph_name(text.index))
- _print_fields(text.x, text.y, text.glyph, glyph_str, text.width)
+ _print_fields(text.x, text.y, text.glyph,
+ text._as_unicode_or_name(), text.width)
if page.boxes:
print("--- BOXES ---")
_print_fields("x", "y", "h", "w")
diff --git a/lib/matplotlib/dviread.pyi b/lib/matplotlib/dviread.pyi
index 12a9215b5308..1c24ff1c28a9 100644
--- a/lib/matplotlib/dviread.pyi
+++ b/lib/matplotlib/dviread.pyi
@@ -6,7 +6,7 @@ from enum import Enum
from collections.abc import Generator
from typing import NamedTuple
-from typing_extensions import Self # < Py 3.11
+from typing import Self
class _dvistate(Enum):
pre = ...
@@ -58,16 +58,28 @@ class Dvi:
class DviFont:
texname: bytes
- size: float
def __init__(
- self, scale: float, tfm: Tfm, texname: bytes, vf: Vf | None
+ self, scale: float, metrics: Tfm | TtfMetrics, texname: bytes, vf: Vf | None
) -> None: ...
+ @classmethod
+ def from_luatex(cls, scale: float, texname: bytes) -> DviFont: ...
+ @classmethod
+ def from_xetex(
+ cls, scale: float, texname: bytes, subfont: int, effects: dict[str, float]
+ ) -> DviFont: ...
def __eq__(self, other: object) -> bool: ...
def __ne__(self, other: object) -> bool: ...
@property
+ def size(self) -> float: ...
+ @property
def widths(self) -> list[int]: ...
@property
def fname(self) -> str: ...
+ def resolve_path(self) -> Path: ...
+ @property
+ def subfont(self) -> int: ...
+ @property
+ def effects(self) -> dict[str, float]: ...
class Vf(Dvi):
def __init__(self, filename: str | os.PathLike) -> None: ...
@@ -93,6 +105,10 @@ class Tfm:
@property
def depth(self) -> dict[int, int]: ...
+class TtfMetrics:
+ def __init__(self, filename: str | os.PathLike) -> None: ...
+ def get_metrics(self, idx: int) -> TexMetrics: ...
+
class PsFont(NamedTuple):
texname: bytes
psname: bytes
diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py
index c15da7597acd..03549dd53bc1 100644
--- a/lib/matplotlib/figure.py
+++ b/lib/matplotlib/figure.py
@@ -3680,7 +3680,7 @@ def figaspect(arg):
w, h = figaspect(2.)
fig = Figure(figsize=(w, h))
- ax = fig.add_axes([0.1, 0.1, 0.8, 0.8])
+ ax = fig.add_axes((0.1, 0.1, 0.8, 0.8))
ax.imshow(A, **kwargs)
Make a figure with the proper aspect for an array::
@@ -3688,7 +3688,7 @@ def figaspect(arg):
A = rand(5, 3)
w, h = figaspect(A)
fig = Figure(figsize=(w, h))
- ax = fig.add_axes([0.1, 0.1, 0.8, 0.8])
+ ax = fig.add_axes((0.1, 0.1, 0.8, 0.8))
ax.imshow(A, **kwargs)
"""
diff --git a/lib/matplotlib/figure.pyi b/lib/matplotlib/figure.pyi
index e7c5175d8af9..59d276362dc5 100644
--- a/lib/matplotlib/figure.pyi
+++ b/lib/matplotlib/figure.pyi
@@ -27,7 +27,7 @@ from matplotlib.text import Text
from matplotlib.transforms import Affine2D, Bbox, BboxBase, Transform
from mpl_toolkits.mplot3d import Axes3D
-from .typing import ColorType, HashableList
+from .typing import ColorType, HashableList, LegendLocType
_T = TypeVar("_T")
@@ -89,19 +89,20 @@ class FigureBase(Artist):
# TODO: docstring indicates SubplotSpec a valid arg, but none of the listed signatures appear to be that
@overload
- def add_subplot(self, *args, projection: Literal["3d"], **kwargs) -> Axes3D: ...
+ def add_subplot(self, *args: Any, projection: Literal["3d"], **kwargs: Any) -> Axes3D: ...
@overload
def add_subplot(
- self, nrows: int, ncols: int, index: int | tuple[int, int], **kwargs
+ self, nrows: int, ncols: int, index: int | tuple[int, int], **kwargs: Any
) -> Axes: ...
@overload
- def add_subplot(self, pos: int, **kwargs) -> Axes: ...
+ def add_subplot(self, pos: int, **kwargs: Any) -> Axes: ...
@overload
- def add_subplot(self, ax: Axes, **kwargs) -> Axes: ...
+ def add_subplot(self, ax: Axes, **kwargs: Any) -> Axes: ...
@overload
- def add_subplot(self, ax: SubplotSpec, **kwargs) -> Axes: ...
+ def add_subplot(self, ax: SubplotSpec, **kwargs: Any) -> Axes: ...
@overload
- def add_subplot(self, **kwargs) -> Axes: ...
+ def add_subplot(self, **kwargs: Any) -> Axes: ...
+
@overload
def subplots(
self,
@@ -151,13 +152,16 @@ class FigureBase(Artist):
@overload
def legend(self) -> Legend: ...
@overload
- def legend(self, handles: Iterable[Artist], labels: Iterable[str], **kwargs) -> Legend: ...
+ def legend(self, handles: Iterable[Artist], labels: Iterable[str],
+ *, loc: LegendLocType | None = ..., **kwargs) -> Legend: ...
@overload
- def legend(self, *, handles: Iterable[Artist], **kwargs) -> Legend: ...
+ def legend(self, *, handles: Iterable[Artist],
+ loc: LegendLocType | None = ..., **kwargs) -> Legend: ...
@overload
- def legend(self, labels: Iterable[str], **kwargs) -> Legend: ...
+ def legend(self, labels: Iterable[str],
+ *, loc: LegendLocType | None = ..., **kwargs) -> Legend: ...
@overload
- def legend(self, **kwargs) -> Legend: ...
+ def legend(self, *, loc: LegendLocType | None = ..., **kwargs) -> Legend: ...
def text(
self,
@@ -190,11 +194,24 @@ class FigureBase(Artist):
def align_labels(self, axs: Iterable[Axes] | None = ...) -> None: ...
def add_gridspec(self, nrows: int = ..., ncols: int = ..., **kwargs) -> GridSpec: ...
@overload
+ def subfigures(
+ self,
+ nrows: int,
+ ncols: int,
+ squeeze: Literal[False],
+ wspace: float | None = ...,
+ hspace: float | None = ...,
+ width_ratios: ArrayLike | None = ...,
+ height_ratios: ArrayLike | None = ...,
+ **kwargs
+ ) -> np.ndarray: ...
+ @overload
def subfigures(
self,
nrows: int = ...,
ncols: int = ...,
- squeeze: Literal[False] = ...,
+ *,
+ squeeze: Literal[False],
wspace: float | None = ...,
hspace: float | None = ...,
width_ratios: ArrayLike | None = ...,
diff --git a/lib/matplotlib/font_manager.py b/lib/matplotlib/font_manager.py
index 2db98b75ab2e..ab6b495631de 100644
--- a/lib/matplotlib/font_manager.py
+++ b/lib/matplotlib/font_manager.py
@@ -35,7 +35,7 @@
from io import BytesIO
import json
import logging
-from numbers import Number
+from numbers import Integral
import os
from pathlib import Path
import plistlib
@@ -172,6 +172,10 @@
]
+def _normalize_weight(weight):
+ return weight if isinstance(weight, Integral) else weight_dict[weight]
+
+
def get_fontext_synonyms(fontext):
"""
Return a list of file extensions that are synonyms for
@@ -1256,8 +1260,8 @@ def score_weight(self, weight1, weight2):
# exact match of the weight names, e.g. weight1 == weight2 == "regular"
if cbook._str_equal(weight1, weight2):
return 0.0
- w1 = weight1 if isinstance(weight1, Number) else weight_dict[weight1]
- w2 = weight2 if isinstance(weight2, Number) else weight_dict[weight2]
+ w1 = _normalize_weight(weight1)
+ w2 = _normalize_weight(weight2)
return 0.95 * (abs(w1 - w2) / 1000) + 0.05
def score_size(self, size1, size2):
@@ -1480,6 +1484,10 @@ def _findfont_cached(self, prop, fontext, directory, fallback_to_default,
best_font = font
if score == 0:
break
+ if best_font is not None and (_normalize_weight(prop.get_weight()) !=
+ _normalize_weight(best_font.weight)):
+ _log.warning('findfont: Failed to find font weight %s, now using %s.',
+ prop.get_weight(), best_font.weight)
if best_font is None or best_score >= 10.0:
if fallback_to_default:
diff --git a/lib/matplotlib/font_manager.pyi b/lib/matplotlib/font_manager.pyi
index c64ddea3e073..e865f67384cd 100644
--- a/lib/matplotlib/font_manager.pyi
+++ b/lib/matplotlib/font_manager.pyi
@@ -1,14 +1,13 @@
+from collections.abc import Iterable
from dataclasses import dataclass
+from numbers import Integral
import os
+from pathlib import Path
+from typing import Any, Literal
from matplotlib._afm import AFM
from matplotlib import ft2font
-from pathlib import Path
-
-from collections.abc import Iterable
-from typing import Any, Literal
-
font_scalings: dict[str | None, float]
stretch_dict: dict[str, int]
weight_dict: dict[str, int]
@@ -19,6 +18,7 @@ MSUserFontDirectories: list[str]
X11FontDirectories: list[str]
OSXFontDirectories: list[str]
+def _normalize_weight(weight: str | Integral) -> Integral: ...
def get_fontext_synonyms(fontext: str) -> list[str]: ...
def list_fonts(directory: str, extensions: Iterable[str]) -> list[str]: ...
def win32FontDirectory() -> str: ...
diff --git a/lib/matplotlib/image.py b/lib/matplotlib/image.py
index 6ca472518b65..c1846f92608c 100644
--- a/lib/matplotlib/image.py
+++ b/lib/matplotlib/image.py
@@ -967,9 +967,9 @@ def set_extent(self, extent, **kwargs):
self.sticky_edges.x[:] = [xmin, xmax]
self.sticky_edges.y[:] = [ymin, ymax]
if self.axes.get_autoscalex_on():
- self.axes.set_xlim((xmin, xmax), auto=None)
+ self.axes.set_xlim(xmin, xmax, auto=None)
if self.axes.get_autoscaley_on():
- self.axes.set_ylim((ymin, ymax), auto=None)
+ self.axes.set_ylim(ymin, ymax, auto=None)
self.stale = True
def get_extent(self):
@@ -1823,7 +1823,7 @@ def thumbnail(infile, thumbfile, scale=0.1, interpolation='bilinear',
fig = Figure(figsize=(width, height), dpi=dpi)
FigureCanvasBase(fig)
- ax = fig.add_axes([0, 0, 1, 1], aspect='auto',
+ ax = fig.add_axes((0, 0, 1, 1), aspect='auto',
frameon=False, xticks=[], yticks=[])
ax.imshow(im, aspect='auto', resample=True, interpolation=interpolation)
fig.savefig(thumbfile, dpi=dpi)
diff --git a/lib/matplotlib/legend.py b/lib/matplotlib/legend.py
index 2fb14e52c58c..119a27181c80 100644
--- a/lib/matplotlib/legend.py
+++ b/lib/matplotlib/legend.py
@@ -576,7 +576,11 @@ def __init__(
# set the text color
color_getters = { # getter function depends on line or patch
- 'linecolor': ['get_color', 'get_facecolor'],
+ 'linecolor': ['get_markerfacecolor',
+ 'get_facecolor',
+ 'get_markeredgecolor',
+ 'get_edgecolor',
+ 'get_color'],
'markerfacecolor': ['get_markerfacecolor', 'get_facecolor'],
'mfc': ['get_markerfacecolor', 'get_facecolor'],
'markeredgecolor': ['get_markeredgecolor', 'get_edgecolor'],
@@ -595,19 +599,22 @@ def __init__(
for getter_name in getter_names:
try:
color = getattr(handle, getter_name)()
- if isinstance(color, np.ndarray):
- if (
- color.shape[0] == 1
- or np.isclose(color, color[0]).all()
- ):
- text.set_color(color[0])
- else:
- pass
- else:
- text.set_color(color)
- break
except AttributeError:
- pass
+ continue
+ if isinstance(color, np.ndarray):
+ if color.size == 0:
+ continue
+ elif (color.shape[0] == 1 or np.isclose(color, color[0]).all()):
+ text.set_color(color[0])
+ else:
+ pass
+ elif cbook._str_lower_equal(color, 'none'):
+ continue
+ elif mpl.colors.to_rgba(color)[3] == 0:
+ continue
+ else:
+ text.set_color(color)
+ break
elif cbook._str_equal(labelcolor, 'none'):
for text in self.texts:
text.set_color(labelcolor)
@@ -1140,9 +1147,10 @@ def _get_anchored_bbox(self, loc, bbox, parentbbox, renderer):
parentbbox : `~matplotlib.transforms.Bbox`
A parent box which will contain the bbox, in display coordinates.
"""
+ pad = self.borderaxespad * renderer.points_to_pixels(self._fontsize)
return offsetbox._get_anchored_bbox(
loc, bbox, parentbbox,
- self.borderaxespad * renderer.points_to_pixels(self._fontsize))
+ pad, pad)
def _find_best_position(self, width, height, renderer):
"""Determine the best location to place the legend."""
diff --git a/lib/matplotlib/legend.pyi b/lib/matplotlib/legend.pyi
index dde5882da69d..c03471fc54d1 100644
--- a/lib/matplotlib/legend.pyi
+++ b/lib/matplotlib/legend.pyi
@@ -14,12 +14,13 @@ from matplotlib.transforms import (
BboxBase,
Transform,
)
+from matplotlib.typing import ColorType, LegendLocType
import pathlib
from collections.abc import Iterable
from typing import Any, Literal, overload
-from .typing import ColorType
+
class DraggableLegend(DraggableOffsetBox):
legend: Legend
@@ -55,7 +56,7 @@ class Legend(Artist):
handles: Iterable[Artist | tuple[Artist, ...]],
labels: Iterable[str],
*,
- loc: str | tuple[float, float] | int | None = ...,
+ loc: LegendLocType | None = ...,
numpoints: int | None = ...,
markerscale: float | None = ...,
markerfirst: bool = ...,
@@ -118,7 +119,7 @@ class Legend(Artist):
def get_texts(self) -> list[Text]: ...
def set_alignment(self, alignment: Literal["center", "left", "right"]) -> None: ...
def get_alignment(self) -> Literal["center", "left", "right"]: ...
- def set_loc(self, loc: str | tuple[float, float] | int | None = ...) -> None: ...
+ def set_loc(self, loc: LegendLocType | None = ...) -> None: ...
def set_title(
self, title: str, prop: FontProperties | str | pathlib.Path | None = ...
) -> None: ...
diff --git a/lib/matplotlib/legend_handler.py b/lib/matplotlib/legend_handler.py
index 263945b050d0..65a78891b17f 100644
--- a/lib/matplotlib/legend_handler.py
+++ b/lib/matplotlib/legend_handler.py
@@ -799,7 +799,6 @@ def get_first(prop_array):
legend_handle.set_linewidth(get_first(orig_handle.get_linewidths()))
legend_handle.set_linestyle(get_first(orig_handle.get_linestyles()))
legend_handle.set_transform(get_first(orig_handle.get_transforms()))
- legend_handle.set_figure(orig_handle.get_figure())
# Alpha is already taken into account by the color attributes.
def create_artists(self, legend, orig_handle,
diff --git a/lib/matplotlib/mlab.py b/lib/matplotlib/mlab.py
index f538b79e44f0..c28774125df0 100644
--- a/lib/matplotlib/mlab.py
+++ b/lib/matplotlib/mlab.py
@@ -48,7 +48,8 @@
"""
import functools
-from numbers import Number
+from numbers import Integral, Number
+import sys
import numpy as np
@@ -210,6 +211,15 @@ def detrend_linear(y):
return y - (b*x + a)
+def _stride_windows(x, n, noverlap=0):
+ _api.check_isinstance(Integral, n=n, noverlap=noverlap)
+ x = np.asarray(x)
+ step = n - noverlap
+ shape = (n, (x.shape[-1]-noverlap)//step)
+ strides = (x.strides[0], step*x.strides[0])
+ return np.lib.stride_tricks.as_strided(x, shape=shape, strides=strides)
+
+
def _spectral_helper(x, y=None, NFFT=None, Fs=None, detrend_func=None,
window=None, noverlap=None, pad_to=None,
sides=None, scale_by_freq=None, mode=None):
@@ -239,7 +249,7 @@ def _spectral_helper(x, y=None, NFFT=None, Fs=None, detrend_func=None,
if NFFT is None:
NFFT = 256
- if noverlap >= NFFT:
+ if not (0 <= noverlap < NFFT):
raise ValueError('noverlap must be less than NFFT')
if mode is None or mode == 'default':
@@ -304,8 +314,12 @@ def _spectral_helper(x, y=None, NFFT=None, Fs=None, detrend_func=None,
raise ValueError(
"The window length must match the data's first dimension")
- result = np.lib.stride_tricks.sliding_window_view(
- x, NFFT, axis=0)[::NFFT - noverlap].T
+ if sys.maxsize > 2**32:
+ result = np.lib.stride_tricks.sliding_window_view(
+ x, NFFT, axis=0)[::NFFT - noverlap].T
+ else:
+ # The NumPy version on 32-bit will OOM, so use old implementation.
+ result = _stride_windows(x, NFFT, noverlap=noverlap)
result = detrend(result, detrend_func, axis=0)
result = result * window.reshape((-1, 1))
result = np.fft.fft(result, n=pad_to, axis=0)[:numFreqs, :]
@@ -313,8 +327,12 @@ def _spectral_helper(x, y=None, NFFT=None, Fs=None, detrend_func=None,
if not same_data:
# if same_data is False, mode must be 'psd'
- resultY = np.lib.stride_tricks.sliding_window_view(
- y, NFFT, axis=0)[::NFFT - noverlap].T
+ if sys.maxsize > 2**32:
+ resultY = np.lib.stride_tricks.sliding_window_view(
+ y, NFFT, axis=0)[::NFFT - noverlap].T
+ else:
+ # The NumPy version on 32-bit will OOM, so use old implementation.
+ resultY = _stride_windows(y, NFFT, noverlap=noverlap)
resultY = detrend(resultY, detrend_func, axis=0)
resultY = resultY * window.reshape((-1, 1))
resultY = np.fft.fft(resultY, n=pad_to, axis=0)[:numFreqs, :]
diff --git a/lib/matplotlib/mpl-data/matplotlibrc b/lib/matplotlib/mpl-data/matplotlibrc
index 780dcd377041..83e567a414c9 100644
--- a/lib/matplotlib/mpl-data/matplotlibrc
+++ b/lib/matplotlib/mpl-data/matplotlibrc
@@ -243,7 +243,7 @@
##
## The font.variant property has two values: normal or small-caps. For
## TrueType fonts, which are scalable fonts, small-caps is equivalent
-## to using a font size of 'smaller', or about 83 % of the current font
+## to using a font size of 'small', or about 83 % of the current font
## size.
##
## The font.weight property has effectively 13 values: normal, bold,
@@ -263,7 +263,7 @@
## special text sizes tick labels, axes, labels, title, etc., see the rc
## settings for axes and ticks. Special text sizes can be defined
## relative to font.size, using the following values: xx-small, x-small,
-## small, medium, large, x-large, xx-large, larger, or smaller
+## small, medium, large, x-large, xx-large
#font.family: sans-serif
#font.style: normal
@@ -543,6 +543,16 @@
#grid.linewidth: 0.8 # in points
#grid.alpha: 1.0 # transparency, between 0.0 and 1.0
+#grid.major.color: None # If None defaults to grid.color
+#grid.major.linestyle: None # If None defaults to grid.linestyle
+#grid.major.linewidth: None # If None defaults to grid.linewidth
+#grid.major.alpha: None # If None defaults to grid.alpha
+
+#grid.minor.color: None # If None defaults to grid.color
+#grid.minor.linestyle: None # If None defaults to grid.linestyle
+#grid.minor.linewidth: None # If None defaults to grid.linewidth
+#grid.minor.alpha: None # If None defaults to grid.alpha
+
## ***************************************************************************
## * LEGEND *
@@ -751,7 +761,7 @@
#svg.fonttype: path # How to handle SVG fonts:
# path: Embed characters as paths -- supported
# by most SVG renderers
- # None: Assume fonts are installed on the
+ # none: Assume fonts are installed on the
# machine where the SVG will be viewed.
#svg.hashsalt: None # If not None, use this string as hash salt instead of uuid4
#svg.id: None # If not None, use this string as the value for the `id`
diff --git a/lib/matplotlib/mpl-data/stylelib/classic.mplstyle b/lib/matplotlib/mpl-data/stylelib/classic.mplstyle
index 92624503f99e..cd636d65c7c8 100644
--- a/lib/matplotlib/mpl-data/stylelib/classic.mplstyle
+++ b/lib/matplotlib/mpl-data/stylelib/classic.mplstyle
@@ -61,7 +61,7 @@ hist.bins : 10
#
# The font.variant property has two values: normal or small-caps. For
# TrueType fonts, which are scalable fonts, small-caps is equivalent
-# to using a font size of 'smaller', or about 83% of the current font
+# to using a font size of 'small', or about 83% of the current font
# size.
#
# The font.weight property has effectively 13 values: normal, bold,
@@ -86,7 +86,7 @@ font.stretch : normal
# special text sizes tick labels, axes, labels, title, etc, see the rc
# settings for axes and ticks. Special text sizes can be defined
# relative to font.size, using the following values: xx-small, x-small,
-# small, medium, large, x-large, xx-large, larger, or smaller
+# small, medium, large, x-large, xx-large
font.size : 12.0
font.serif : DejaVu Serif, New Century Schoolbook, Century Schoolbook L, Utopia, ITC Bookman, Bookman, Nimbus Roman No9 L, Times New Roman, Times, Palatino, Charter, serif
font.sans-serif: DejaVu Sans, Lucida Grande, Verdana, Geneva, Lucid, Arial, Helvetica, Avant Garde, sans-serif
diff --git a/lib/matplotlib/offsetbox.py b/lib/matplotlib/offsetbox.py
index 1e07125cdc2a..39035e0b785a 100644
--- a/lib/matplotlib/offsetbox.py
+++ b/lib/matplotlib/offsetbox.py
@@ -201,7 +201,7 @@ def _get_aligned_offsets(yspans, height, align="baseline"):
class OffsetBox(martist.Artist):
"""
- The OffsetBox is a simple container artist.
+ A simple container artist.
The child artists are meant to be drawn at a relative position to its
parent.
@@ -826,17 +826,18 @@ def draw(self, renderer):
class AuxTransformBox(OffsetBox):
"""
- Offset Box with the aux_transform. Its children will be
- transformed with the aux_transform first then will be
- offsetted. The absolute coordinate of the aux_transform is meaning
- as it will be automatically adjust so that the left-lower corner
- of the bounding box of children will be set to (0, 0) before the
- offset transform.
-
- It is similar to drawing area, except that the extent of the box
- is not predetermined but calculated from the window extent of its
- children. Furthermore, the extent of the children will be
- calculated in the transformed coordinate.
+ An OffsetBox with an auxiliary transform.
+
+ All child artists are first transformed with *aux_transform*, then
+ translated with an offset (the same for all children) so the bounding
+ box of the children matches the drawn box. (In other words, adding an
+ arbitrary translation to *aux_transform* has no effect as it will be
+ cancelled out by the later offsetting.)
+
+ `AuxTransformBox` is similar to `.DrawingArea`, except that the extent of
+ the box is not predetermined but calculated from the window extent of its
+ children, and the extent of the children will be calculated in the
+ transformed coordinate.
"""
def __init__(self, aux_transform):
self.aux_transform = aux_transform
@@ -853,10 +854,7 @@ def add_artist(self, a):
self.stale = True
def get_transform(self):
- """
- Return the :class:`~matplotlib.transforms.Transform` applied
- to the children
- """
+ """Return the `.Transform` applied to the children."""
return (self.aux_transform
+ self.ref_offset_transform
+ self.offset_transform)
@@ -908,7 +906,7 @@ def draw(self, renderer):
class AnchoredOffsetbox(OffsetBox):
"""
- An offset box placed according to location *loc*.
+ An OffsetBox placed according to location *loc*.
AnchoredOffsetbox has a single child. When multiple children are needed,
use an extra OffsetBox to enclose them. By default, the offset box is
@@ -948,8 +946,13 @@ def __init__(self, loc, *,
See the parameter *loc* of `.Legend` for details.
pad : float, default: 0.4
Padding around the child as fraction of the fontsize.
- borderpad : float, default: 0.5
+ borderpad : float or (float, float), default: 0.5
Padding between the offsetbox frame and the *bbox_to_anchor*.
+ If a float, the same padding is used for both x and y.
+ If a tuple of two floats, it specifies the (x, y) padding.
+
+ .. versionadded:: 3.11
+ The *borderpad* parameter now accepts a tuple of (x, y) paddings.
child : `.OffsetBox`
The box that will be anchored.
prop : `.FontProperties`
@@ -1056,12 +1059,22 @@ def set_bbox_to_anchor(self, bbox, transform=None):
@_compat_get_offset
def get_offset(self, bbox, renderer):
# docstring inherited
- pad = (self.borderpad
- * renderer.points_to_pixels(self.prop.get_size_in_points()))
+ fontsize_in_pixels = renderer.points_to_pixels(self.prop.get_size_in_points())
+ try:
+ borderpad_x, borderpad_y = self.borderpad
+ except TypeError:
+ borderpad_x = self.borderpad
+ borderpad_y = self.borderpad
+ pad_x_pixels = borderpad_x * fontsize_in_pixels
+ pad_y_pixels = borderpad_y * fontsize_in_pixels
bbox_to_anchor = self.get_bbox_to_anchor()
x0, y0 = _get_anchored_bbox(
- self.loc, Bbox.from_bounds(0, 0, bbox.width, bbox.height),
- bbox_to_anchor, pad)
+ self.loc,
+ Bbox.from_bounds(0, 0, bbox.width, bbox.height),
+ bbox_to_anchor,
+ pad_x_pixels,
+ pad_y_pixels
+ )
return x0 - bbox.x0, y0 - bbox.y0
def update_frame(self, bbox, fontsize=None):
@@ -1086,15 +1099,15 @@ def draw(self, renderer):
self.stale = False
-def _get_anchored_bbox(loc, bbox, parentbbox, borderpad):
+def _get_anchored_bbox(loc, bbox, parentbbox, pad_x, pad_y):
"""
Return the (x, y) position of the *bbox* anchored at the *parentbbox* with
- the *loc* code with the *borderpad*.
+ the *loc* code with the *borderpad* and padding *pad_x*, *pad_y*.
"""
# This is only called internally and *loc* should already have been
# validated. If 0 (None), we just let ``bbox.anchored`` raise.
c = [None, "NE", "NW", "SW", "SE", "E", "W", "E", "S", "N", "C"][loc]
- container = parentbbox.padded(-borderpad)
+ container = parentbbox.padded(-pad_x, -pad_y)
return bbox.anchored(c, container=container).p0
diff --git a/lib/matplotlib/offsetbox.pyi b/lib/matplotlib/offsetbox.pyi
index 8a2016c0320a..36f31908eebf 100644
--- a/lib/matplotlib/offsetbox.pyi
+++ b/lib/matplotlib/offsetbox.pyi
@@ -157,7 +157,7 @@ class AnchoredOffsetbox(OffsetBox):
loc: str,
*,
pad: float = ...,
- borderpad: float = ...,
+ borderpad: float | tuple[float, float] = ...,
child: OffsetBox | None = ...,
prop: FontProperties | None = ...,
frameon: bool = ...,
@@ -185,7 +185,7 @@ class AnchoredText(AnchoredOffsetbox):
loc: str,
*,
pad: float = ...,
- borderpad: float = ...,
+ borderpad: float | tuple[float, float] = ...,
prop: dict[str, Any] | None = ...,
**kwargs
) -> None: ...
diff --git a/lib/matplotlib/patches.py b/lib/matplotlib/patches.py
index 63453d416b99..d750e86e401f 100644
--- a/lib/matplotlib/patches.py
+++ b/lib/matplotlib/patches.py
@@ -459,7 +459,8 @@ def set_linewidth(self, w):
w : float or None
"""
w = mpl._val_or_rc(w, 'patch.linewidth')
- self._linewidth = float(w)
+ w = float(w)
+ self._linewidth = w
self._dash_pattern = mlines._scale_dashes(*self._unscaled_dash_pattern, w)
self.stale = True
@@ -1538,7 +1539,7 @@ def _make_verts(self):
length = distance
else:
length = distance + head_length
- if not length:
+ if np.size(length) == 0:
self.verts = np.empty([0, 2]) # display nothing if empty
else:
# start by drawing horizontal arrow, point at (0, 0)
diff --git a/lib/matplotlib/path.py b/lib/matplotlib/path.py
index a021706fb1e5..f65ade669167 100644
--- a/lib/matplotlib/path.py
+++ b/lib/matplotlib/path.py
@@ -275,17 +275,37 @@ def copy(self):
"""
return copy.copy(self)
- def __deepcopy__(self, memo=None):
+ def __deepcopy__(self, memo):
"""
Return a deepcopy of the `Path`. The `Path` will not be
readonly, even if the source `Path` is.
"""
# Deepcopying arrays (vertices, codes) strips the writeable=False flag.
- p = copy.deepcopy(super(), memo)
+ cls = type(self)
+ memo[id(self)] = p = cls.__new__(cls)
+
+ for k, v in self.__dict__.items():
+ setattr(p, k, copy.deepcopy(v, memo))
+
p._readonly = False
return p
- deepcopy = __deepcopy__
+ def deepcopy(self, memo=None):
+ """
+ Return a deep copy of the `Path`. The `Path` will not be readonly,
+ even if the source `Path` is.
+
+ Parameters
+ ----------
+ memo : dict, optional
+ A dictionary to use for memoizing, passed to `copy.deepcopy`.
+
+ Returns
+ -------
+ Path
+ A deep copy of the `Path`, but not readonly.
+ """
+ return copy.deepcopy(self, memo)
@classmethod
def make_compound_path_from_polys(cls, XY):
diff --git a/lib/matplotlib/path.pyi b/lib/matplotlib/path.pyi
index 464fc6d9a912..8a5a5c03792e 100644
--- a/lib/matplotlib/path.pyi
+++ b/lib/matplotlib/path.pyi
@@ -44,8 +44,8 @@ class Path:
@property
def readonly(self) -> bool: ...
def copy(self) -> Path: ...
- def __deepcopy__(self, memo: dict[int, Any] | None = ...) -> Path: ...
- deepcopy = __deepcopy__
+ def __deepcopy__(self, memo: dict[int, Any]) -> Path: ...
+ def deepcopy(self, memo: dict[int, Any] | None = None) -> Path: ...
@classmethod
def make_compound_path_from_polys(cls, XY: ArrayLike) -> Path: ...
diff --git a/lib/matplotlib/projections/polar.py b/lib/matplotlib/projections/polar.py
index 948b3a6e704f..8b0a01f556e3 100644
--- a/lib/matplotlib/projections/polar.py
+++ b/lib/matplotlib/projections/polar.py
@@ -207,7 +207,7 @@ def transform_non_affine(self, values):
# docstring inherited
x, y = values.T
r = np.hypot(x, y)
- theta = (np.arctan2(y, x) + 2 * np.pi) % (2 * np.pi)
+ theta = np.arctan2(y, x) % (2 * np.pi)
if self._use_rmin and self._axis is not None:
r += self._axis.get_rorigin()
r *= self._axis.get_rsign()
@@ -431,6 +431,7 @@ class RadialLocator(mticker.Locator):
scale of the *r*-axis).
"""
+ @_api.delete_parameter("3.11", "axes")
def __init__(self, base, axes=None):
self.base = base
self._axes = axes
@@ -440,11 +441,11 @@ def set_axis(self, axis):
def __call__(self):
# Ensure previous behaviour with full circle non-annular views.
- if self._axes:
- if _is_full_circle_rad(*self._axes.viewLim.intervalx):
- rorigin = self._axes.get_rorigin() * self._axes.get_rsign()
- if self._axes.get_rmin() <= rorigin:
- return [tick for tick in self.base() if tick > rorigin]
+ ax = self.base.axis.axes
+ if _is_full_circle_rad(*ax.viewLim.intervalx):
+ rorigin = ax.get_rorigin() * ax.get_rsign()
+ if ax.get_rmin() <= rorigin:
+ return [tick for tick in self.base() if tick > rorigin]
return self.base()
def _zero_in_bounds(self):
@@ -452,7 +453,7 @@ def _zero_in_bounds(self):
Return True if zero is within the valid values for the
scale of the radial axis.
"""
- vmin, vmax = self._axes.yaxis._scale.limit_range_for_scale(0, 1, 1e-5)
+ vmin, vmax = self.base.axis._scale.limit_range_for_scale(0, 1, 1e-5)
return vmin == 0
def nonsingular(self, vmin, vmax):
@@ -679,20 +680,15 @@ def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.sticky_edges.y.append(0)
- def _wrap_locator_formatter(self):
- self.set_major_locator(RadialLocator(self.get_major_locator(),
- self.axes))
- self.isDefault_majloc = True
+ def set_major_locator(self, locator):
+ if not isinstance(locator, RadialLocator):
+ locator = RadialLocator(locator)
+ super().set_major_locator(locator)
def clear(self):
# docstring inherited
super().clear()
self.set_ticks_position('none')
- self._wrap_locator_formatter()
-
- def _set_scale(self, value, **kwargs):
- super()._set_scale(value, **kwargs)
- self._wrap_locator_formatter()
def _is_full_circle_deg(thetamin, thetamax):
@@ -817,6 +813,10 @@ def _init_axis(self):
self.xaxis = ThetaAxis(self, clear=False)
self.yaxis = RadialAxis(self, clear=False)
self.spines['polar'].register_axis(self.yaxis)
+ inner_spine = self.spines.get('inner', None)
+ if inner_spine is not None:
+ # Subclasses may not have inner spine.
+ inner_spine.register_axis(self.yaxis)
def _set_lim_and_transforms(self):
# A view limit where the minimum radius can be locked if the user
@@ -961,7 +961,9 @@ def draw(self, renderer):
thetamin, thetamax = np.rad2deg(self._realViewLim.intervalx)
if thetamin > thetamax:
thetamin, thetamax = thetamax, thetamin
- rmin, rmax = ((self._realViewLim.intervaly - self.get_rorigin()) *
+ rscale_tr = self.yaxis.get_transform()
+ rmin, rmax = ((rscale_tr.transform(self._realViewLim.intervaly) -
+ rscale_tr.transform(self.get_rorigin())) *
self.get_rsign())
if isinstance(self.patch, mpatches.Wedge):
# Backwards-compatibility: Any subclassed Axes might override the
@@ -1242,19 +1244,11 @@ def set_rlabel_position(self, value):
"""
self._r_label_position.clear().translate(np.deg2rad(value), 0.0)
- def set_yscale(self, *args, **kwargs):
- super().set_yscale(*args, **kwargs)
- self.yaxis.set_major_locator(
- self.RadialLocator(self.yaxis.get_major_locator(), self))
-
def set_rscale(self, *args, **kwargs):
return Axes.set_yscale(self, *args, **kwargs)
def set_rticks(self, *args, **kwargs):
- result = Axes.set_yticks(self, *args, **kwargs)
- self.yaxis.set_major_locator(
- self.RadialLocator(self.yaxis.get_major_locator(), self))
- return result
+ return Axes.set_yticks(self, *args, **kwargs)
def set_thetagrids(self, angles, labels=None, fmt=None, **kwargs):
"""
diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py
index cf5c9b4b739f..4531cbd3b261 100644
--- a/lib/matplotlib/pyplot.py
+++ b/lib/matplotlib/pyplot.py
@@ -50,7 +50,7 @@
import sys
import threading
import time
-from typing import TYPE_CHECKING, cast, overload
+from typing import IO, TYPE_CHECKING, cast, overload
from cycler import cycler # noqa: F401
import matplotlib
@@ -100,7 +100,14 @@
import matplotlib.backend_bases
from matplotlib.axis import Tick
from matplotlib.axes._base import _AxesBase
- from matplotlib.backend_bases import Event
+ from matplotlib.backend_bases import (
+ CloseEvent,
+ DrawEvent,
+ KeyEvent,
+ MouseEvent,
+ PickEvent,
+ ResizeEvent,
+ )
from matplotlib.cm import ScalarMappable
from matplotlib.contour import ContourSet, QuadContourSet
from matplotlib.collections import (
@@ -126,11 +133,18 @@
from matplotlib.quiver import Barbs, Quiver, QuiverKey
from matplotlib.scale import ScaleBase
from matplotlib.typing import (
+ CloseEventType,
ColorType,
CoordsType,
+ DrawEventType,
HashableList,
+ KeyEventType,
LineStyleType,
MarkerType,
+ MouseEventType,
+ PickEventType,
+ ResizeEventType,
+ LogLevel
)
from matplotlib.widgets import SubplotTool
@@ -338,8 +352,8 @@ def uninstall_repl_displayhook() -> None:
# Ensure this appears in the pyplot docs.
@_copy_docstring_and_deprecators(matplotlib.set_loglevel)
-def set_loglevel(*args, **kwargs) -> None:
- return matplotlib.set_loglevel(*args, **kwargs)
+def set_loglevel(level: LogLevel) -> None:
+ return matplotlib.set_loglevel(level)
@_copy_docstring_and_deprecators(Artist.findobj)
@@ -569,6 +583,14 @@ def draw_if_interactive(*args, **kwargs):
return _get_backend_mod().draw_if_interactive(*args, **kwargs)
+@overload
+def show(*, block: bool, **kwargs) -> None: ...
+
+
+@overload
+def show(*args: Any, **kwargs: Any) -> None: ...
+
+
# This function's signature is rewritten upon backend-load by switch_backend.
def show(*args, **kwargs) -> None:
"""
@@ -1167,8 +1189,32 @@ def get_current_fig_manager() -> FigureManagerBase | None:
return gcf().canvas.manager
+@overload
+def connect(s: MouseEventType, func: Callable[[MouseEvent], Any]) -> int: ...
+
+
+@overload
+def connect(s: KeyEventType, func: Callable[[KeyEvent], Any]) -> int: ...
+
+
+@overload
+def connect(s: PickEventType, func: Callable[[PickEvent], Any]) -> int: ...
+
+
+@overload
+def connect(s: ResizeEventType, func: Callable[[ResizeEvent], Any]) -> int: ...
+
+
+@overload
+def connect(s: CloseEventType, func: Callable[[CloseEvent], Any]) -> int: ...
+
+
+@overload
+def connect(s: DrawEventType, func: Callable[[DrawEvent], Any]) -> int: ...
+
+
@_copy_docstring_and_deprecators(FigureCanvasBase.mpl_connect)
-def connect(s: str, func: Callable[[Event], Any]) -> int:
+def connect(s, func) -> int:
return gcf().canvas.mpl_connect(s, func)
@@ -1251,11 +1297,11 @@ def draw() -> None:
@_copy_docstring_and_deprecators(Figure.savefig)
-def savefig(*args, **kwargs) -> None:
+def savefig(fname: str | os.PathLike | IO, **kwargs) -> None:
fig = gcf()
# savefig default implementation has no return, so mypy is unhappy
# presumably this is here because subclasses can return?
- res = fig.savefig(*args, **kwargs) # type: ignore[func-returns-value]
+ res = fig.savefig(fname, **kwargs) # type: ignore[func-returns-value]
fig.canvas.draw_idle() # Need this if 'transparent=True', to reset colors.
return res
@@ -1295,7 +1341,7 @@ def axes(
- *None*: A new full window Axes is added using
``subplot(**kwargs)``.
- - 4-tuple of floats *rect* = ``(left, bottom, width, height)``.
+ - 4-tuple of float *rect* = ``(left, bottom, width, height)``.
A new Axes is added with dimensions *rect* in normalized
(0, 1) units using `~.Figure.add_axes` on the current figure.
@@ -1393,6 +1439,18 @@ def cla() -> None:
## More ways of creating Axes ##
+@overload
+def subplot(nrows: int, ncols: int, index: int, /, **kwargs): ...
+
+
+@overload
+def subplot(pos: int | SubplotSpec, /, **kwargs): ...
+
+
+@overload
+def subplot(**kwargs): ...
+
+
@_docstring.interpd
def subplot(*args, **kwargs) -> Axes:
"""
@@ -1406,7 +1464,6 @@ def subplot(*args, **kwargs) -> Axes:
subplot(nrows, ncols, index, **kwargs)
subplot(pos, **kwargs)
subplot(**kwargs)
- subplot(ax)
Parameters
----------
@@ -2096,6 +2153,24 @@ def box(on: bool | None = None) -> None:
## Axis ##
+@overload
+def xlim() -> tuple[float, float]:
+ ...
+
+
+@overload
+def xlim(
+ left: float | tuple[float, float] | None = None,
+ right: float | None = None,
+ *,
+ emit: bool = True,
+ auto: bool | None = False,
+ xmin: float | None = None,
+ xmax: float | None = None,
+) -> tuple[float, float]:
+ ...
+
+
def xlim(*args, **kwargs) -> tuple[float, float]:
"""
Get or set the x limits of the current Axes.
@@ -2133,6 +2208,24 @@ def xlim(*args, **kwargs) -> tuple[float, float]:
return ret
+@overload
+def ylim() -> tuple[float, float]:
+ ...
+
+
+@overload
+def ylim(
+ bottom: float | tuple[float, float] | None = None,
+ top: float | None = None,
+ *,
+ emit: bool = True,
+ auto: bool | None = False,
+ ymin: float | None = None,
+ ymax: float | None = None,
+) -> tuple[float, float]:
+ ...
+
+
def ylim(*args, **kwargs) -> tuple[float, float]:
"""
Get or set the y-limits of the current Axes.
@@ -3129,12 +3222,17 @@ def boxplot(
def broken_barh(
xranges: Sequence[tuple[float, float]],
yrange: tuple[float, float],
+ align: Literal["bottom", "center", "top"] = "bottom",
*,
data=None,
**kwargs,
) -> PolyCollection:
return gca().broken_barh(
- xranges, yrange, **({"data": data} if data is not None else {}), **kwargs
+ xranges,
+ yrange,
+ align=align,
+ **({"data": data} if data is not None else {}),
+ **kwargs,
)
diff --git a/lib/matplotlib/rcsetup.py b/lib/matplotlib/rcsetup.py
index 02e3601ff4c2..80d25659888e 100644
--- a/lib/matplotlib/rcsetup.py
+++ b/lib/matplotlib/rcsetup.py
@@ -361,6 +361,12 @@ def validate_color(s):
raise ValueError(f'{s!r} does not look like a color arg')
+def _validate_color_or_None(s):
+ if s is None or cbook._str_equal(s, "None"):
+ return None
+ return validate_color(s)
+
+
validate_colorlist = _listify_validator(
validate_color, allow_stringlist=True, doc='return a list of colorspecs')
@@ -515,6 +521,13 @@ def _is_iterable_not_string_like(x):
raise ValueError(f"linestyle {ls!r} is not a valid on-off ink sequence.")
+def _validate_linestyle_or_None(s):
+ if s is None or cbook._str_equal(s, "None"):
+ return None
+
+ return _validate_linestyle(s)
+
+
validate_fillstyle = ValidateInStrings(
'markers.fillstyle', ['full', 'left', 'right', 'bottom', 'top', 'none'])
@@ -1242,6 +1255,16 @@ def _convert_validator_spec(key, conv):
"grid.linewidth": validate_float, # in points
"grid.alpha": validate_float,
+ "grid.major.color": _validate_color_or_None, # grid color
+ "grid.major.linestyle": _validate_linestyle_or_None, # solid
+ "grid.major.linewidth": validate_float_or_None, # in points
+ "grid.major.alpha": validate_float_or_None,
+
+ "grid.minor.color": _validate_color_or_None, # grid color
+ "grid.minor.linestyle": _validate_linestyle_or_None, # solid
+ "grid.minor.linewidth": validate_float_or_None, # in points
+ "grid.minor.alpha": validate_float_or_None,
+
## figure props
# figure title
"figure.titlesize": validate_fontsize,
diff --git a/lib/matplotlib/rcsetup.pyi b/lib/matplotlib/rcsetup.pyi
index eb1d7c9f3a33..c6611845723d 100644
--- a/lib/matplotlib/rcsetup.pyi
+++ b/lib/matplotlib/rcsetup.pyi
@@ -48,6 +48,7 @@ def validate_color_or_auto(s: Any) -> ColorType | Literal["auto"]: ...
def _validate_color_or_edge(s: Any) -> ColorType | Literal["edge"]: ...
def validate_color_for_prop_cycle(s: Any) -> ColorType: ...
def validate_color(s: Any) -> ColorType: ...
+def _validate_color_or_None(s: Any) -> ColorType | None: ...
def validate_colorlist(s: Any) -> list[ColorType]: ...
def _validate_color_or_linecolor(
s: Any,
@@ -137,6 +138,7 @@ def validate_fillstylelist(
) -> list[Literal["full", "left", "right", "bottom", "top", "none"]]: ...
def validate_markevery(s: Any) -> MarkEveryType: ...
def _validate_linestyle(s: Any) -> LineStyleType: ...
+def _validate_linestyle_or_None(s: Any) -> LineStyleType | None: ...
def validate_markeverylist(s: Any) -> list[MarkEveryType]: ...
def validate_bbox(s: Any) -> Literal["tight", "standard"] | None: ...
def validate_sketch(s: Any) -> None | tuple[float, float, float]: ...
diff --git a/lib/matplotlib/sankey.pyi b/lib/matplotlib/sankey.pyi
index 33565b998a9c..083d590559ca 100644
--- a/lib/matplotlib/sankey.pyi
+++ b/lib/matplotlib/sankey.pyi
@@ -2,7 +2,7 @@ from matplotlib.axes import Axes
from collections.abc import Callable, Iterable
from typing import Any
-from typing_extensions import Self # < Py 3.11
+from typing import Self
import numpy as np
diff --git a/lib/matplotlib/scale.py b/lib/matplotlib/scale.py
index 44fbe5209c4d..f6ccc42442d6 100644
--- a/lib/matplotlib/scale.py
+++ b/lib/matplotlib/scale.py
@@ -31,6 +31,7 @@
import inspect
import textwrap
+from functools import wraps
import numpy as np
@@ -74,9 +75,20 @@ def __init__(self, axis):
The following note is for scale implementers.
For back-compatibility reasons, scales take an `~matplotlib.axis.Axis`
- object as first argument. However, this argument should not
- be used: a single scale object should be usable by multiple
- `~matplotlib.axis.Axis`\es at the same time.
+ object as the first argument.
+
+ .. deprecated:: 3.11
+
+ The *axis* parameter is now optional, i.e. matplotlib is compatible
+ with `.ScaleBase` subclasses that do not take an *axis* parameter.
+
+ The *axis* parameter is pending-deprecated. It will be deprecated
+ in matplotlib 3.13, and removed in matplotlib 3.15.
+
+ 3rd-party scales are recommended to remove the *axis* parameter now
+ if they can afford to restrict compatibility to matplotlib >= 3.11
+ already. Otherwise, they may keep the *axis* parameter and remove it
+ in time for matplotlib 3.13.
"""
def get_transform(self):
@@ -103,6 +115,53 @@ def limit_range_for_scale(self, vmin, vmax, minpos):
return vmin, vmax
+def _make_axis_parameter_optional(init_func):
+ """
+ Decorator to allow leaving out the *axis* parameter in scale constructors.
+
+ This decorator ensures backward compatibility for scale classes that
+ previously required an *axis* parameter. It allows constructors to be
+ callerd with or without the *axis* parameter.
+
+ For simplicity, this does not handle the case when *axis*
+ is passed as a keyword. However,
+ scanning GitHub, there's no evidence that that is used anywhere.
+
+ Parameters
+ ----------
+ init_func : callable
+ The original __init__ method of a scale class.
+
+ Returns
+ -------
+ callable
+ A wrapped version of *init_func* that handles the optional *axis*.
+
+ Notes
+ -----
+ If the wrapped constructor defines *axis* as its first argument, the
+ parameter is preserved when present. Otherwise, the value `None` is injected
+ as the first argument.
+
+ Examples
+ --------
+ >>> from matplotlib.scale import ScaleBase
+ >>> class CustomScale(ScaleBase):
+ ... @_make_axis_parameter_optional
+ ... def __init__(self, axis, custom_param=1):
+ ... self.custom_param = custom_param
+ """
+ @wraps(init_func)
+ def wrapper(self, *args, **kwargs):
+ if args and isinstance(args[0], mpl.axis.Axis):
+ return init_func(self, *args, **kwargs)
+ else:
+ # Remove 'axis' from kwargs to avoid double assignment
+ axis = kwargs.pop('axis', None)
+ return init_func(self, axis, *args, **kwargs)
+ return wrapper
+
+
class LinearScale(ScaleBase):
"""
The default linear scale.
@@ -110,6 +169,7 @@ class LinearScale(ScaleBase):
name = 'linear'
+ @_make_axis_parameter_optional
def __init__(self, axis):
# This method is present only to prevent inheritance of the base class'
# constructor docstring, which would otherwise end up interpolated into
@@ -180,12 +240,19 @@ class FuncScale(ScaleBase):
name = 'function'
+ @_make_axis_parameter_optional
def __init__(self, axis, functions):
"""
Parameters
----------
axis : `~matplotlib.axis.Axis`
The axis for the scale.
+
+ .. note::
+ This parameter is unused and will be removed in an imminent release.
+ It can already be left out because of special preprocessing,
+ so that ``FuncScale(functions)`` is valid.
+
functions : (callable, callable)
two-tuple of the forward and inverse functions for the scale.
The forward function must be monotonic.
@@ -279,12 +346,19 @@ class LogScale(ScaleBase):
"""
name = 'log'
- def __init__(self, axis, *, base=10, subs=None, nonpositive="clip"):
+ @_make_axis_parameter_optional
+ def __init__(self, axis=None, *, base=10, subs=None, nonpositive="clip"):
"""
Parameters
----------
axis : `~matplotlib.axis.Axis`
The axis for the scale.
+
+ .. note::
+ This parameter is unused and about to be removed in the future.
+ It can already now be left out because of special preprocessing,
+ so that ``LogScale(base=2)`` is valid.
+
base : float, default: 10
The base of the logarithm.
nonpositive : {'clip', 'mask'}, default: 'clip'
@@ -330,6 +404,7 @@ class FuncScaleLog(LogScale):
name = 'functionlog'
+ @_make_axis_parameter_optional
def __init__(self, axis, functions, base=10):
"""
Parameters
@@ -433,6 +508,14 @@ class SymmetricalLogScale(ScaleBase):
Parameters
----------
+ axis : `~matplotlib.axis.Axis`
+ The axis for the scale.
+
+ .. note::
+ This parameter is unused and about to be removed in the future.
+ It can already now be left out because of special preprocessing,
+ so that ``SymmetricalLocSacle(base=2)`` is valid.
+
base : float, default: 10
The base of the logarithm.
@@ -455,7 +538,8 @@ class SymmetricalLogScale(ScaleBase):
"""
name = 'symlog'
- def __init__(self, axis, *, base=10, linthresh=2, subs=None, linscale=1):
+ @_make_axis_parameter_optional
+ def __init__(self, axis=None, *, base=10, linthresh=2, subs=None, linscale=1):
self._transform = SymmetricalLogTransform(base, linthresh, linscale)
self.subs = subs
@@ -547,11 +631,20 @@ class AsinhScale(ScaleBase):
1024: (256, 512)
}
- def __init__(self, axis, *, linear_width=1.0,
+ @_make_axis_parameter_optional
+ def __init__(self, axis=None, *, linear_width=1.0,
base=10, subs='auto', **kwargs):
"""
Parameters
----------
+ axis : `~matplotlib.axis.Axis`
+ The axis for the scale.
+
+ .. note::
+ This parameter is unused and about to be removed in the future.
+ It can already now be left out because of special preprocessing,
+ so that ``AsinhScale()`` is valid.
+
linear_width : float, default: 1
The scale parameter (elsewhere referred to as :math:`a_0`)
defining the extent of the quasi-linear region,
@@ -645,13 +738,20 @@ class LogitScale(ScaleBase):
"""
name = 'logit'
- def __init__(self, axis, nonpositive='mask', *,
+ @_make_axis_parameter_optional
+ def __init__(self, axis=None, nonpositive='mask', *,
one_half=r"\frac{1}{2}", use_overline=False):
r"""
Parameters
----------
axis : `~matplotlib.axis.Axis`
- Currently unused.
+ The axis for the scale.
+
+ .. note::
+ This parameter is unused and about to be removed in the future.
+ It can already now be left out because of special preprocessing,
+ so that ``LogitScale()`` is valid.
+
nonpositive : {'mask', 'clip'}
Determines the behavior for values beyond the open interval ]0, 1[.
They can either be masked as invalid, or clipped to a number very
@@ -709,6 +809,20 @@ def limit_range_for_scale(self, vmin, vmax, minpos):
'functionlog': FuncScaleLog,
}
+# caching of signature info
+# For backward compatibility, the built-in scales will keep the *axis* parameter
+# in their constructors until matplotlib 3.15, i.e. as long as the *axis* parameter
+# is still supported.
+_scale_has_axis_parameter = {
+ 'linear': True,
+ 'log': True,
+ 'symlog': True,
+ 'asinh': True,
+ 'logit': True,
+ 'function': True,
+ 'functionlog': True,
+}
+
def get_scale_names():
"""Return the names of the available scales."""
@@ -725,7 +839,11 @@ def scale_factory(scale, axis, **kwargs):
axis : `~matplotlib.axis.Axis`
"""
scale_cls = _api.check_getitem(_scale_mapping, scale=scale)
- return scale_cls(axis, **kwargs)
+
+ if _scale_has_axis_parameter[scale]:
+ return scale_cls(axis, **kwargs)
+ else:
+ return scale_cls(**kwargs)
if scale_factory.__doc__:
@@ -744,6 +862,20 @@ def register_scale(scale_class):
"""
_scale_mapping[scale_class.name] = scale_class
+ # migration code to handle the *axis* parameter
+ has_axis_parameter = "axis" in inspect.signature(scale_class).parameters
+ _scale_has_axis_parameter[scale_class.name] = has_axis_parameter
+ if has_axis_parameter:
+ _api.warn_deprecated(
+ "3.11",
+ message=f"The scale {scale_class.__qualname__!r} uses an 'axis' parameter "
+ "in the constructors. This parameter is pending-deprecated since "
+ "matplotlib 3.11. It will be fully deprecated in 3.13 and removed "
+ "in 3.15. Starting with 3.11, 'register_scale()' accepts scales "
+ "without the *axis* parameter.",
+ pending=True,
+ )
+
def _get_scale_docs():
"""
diff --git a/lib/matplotlib/scale.pyi b/lib/matplotlib/scale.pyi
index 7fec8e68cc5a..ba9f269b8c78 100644
--- a/lib/matplotlib/scale.pyi
+++ b/lib/matplotlib/scale.pyi
@@ -15,6 +15,10 @@ class ScaleBase:
class LinearScale(ScaleBase):
name: str
+ def __init__(
+ self,
+ axis: Axis | None,
+ ) -> None: ...
class FuncTransform(Transform):
input_dims: int
@@ -57,7 +61,7 @@ class LogScale(ScaleBase):
subs: Iterable[int] | None
def __init__(
self,
- axis: Axis | None,
+ axis: Axis | None = ...,
*,
base: float = ...,
subs: Iterable[int] | None = ...,
@@ -104,7 +108,7 @@ class SymmetricalLogScale(ScaleBase):
subs: Iterable[int] | None
def __init__(
self,
- axis: Axis | None,
+ axis: Axis | None = ...,
*,
base: float = ...,
linthresh: float = ...,
@@ -138,7 +142,7 @@ class AsinhScale(ScaleBase):
auto_tick_multipliers: dict[int, tuple[int, ...]]
def __init__(
self,
- axis: Axis | None,
+ axis: Axis | None = ...,
*,
linear_width: float = ...,
base: float = ...,
@@ -165,7 +169,7 @@ class LogitScale(ScaleBase):
name: str
def __init__(
self,
- axis: Axis | None,
+ axis: Axis | None = ...,
nonpositive: Literal["mask", "clip"] = ...,
*,
one_half: str = ...,
@@ -176,3 +180,4 @@ class LogitScale(ScaleBase):
def get_scale_names() -> list[str]: ...
def scale_factory(scale: str, axis: Axis, **kwargs) -> ScaleBase: ...
def register_scale(scale_class: type[ScaleBase]) -> None: ...
+def _make_axis_parameter_optional(init_func: Callable[..., None]) -> Callable[..., None]: ...
diff --git a/lib/matplotlib/sphinxext/plot_directive.py b/lib/matplotlib/sphinxext/plot_directive.py
index af858e344afa..b5f10d851182 100644
--- a/lib/matplotlib/sphinxext/plot_directive.py
+++ b/lib/matplotlib/sphinxext/plot_directive.py
@@ -47,6 +47,12 @@
The ``.. plot::`` directive supports the following options:
+``:filename-prefix:`` : str
+ The base name (without the extension) of the outputted image and script
+ files. The default is to use the same name as the input script, or the
+ name of the RST document if no script is provided. The filename-prefix for
+ each plot directive must be unique.
+
``:format:`` : {'python', 'doctest'}
The format of the input. If unset, the format is auto-detected.
@@ -163,8 +169,10 @@
be customized by changing the *plot_template*. See the source of
:doc:`/api/sphinxext_plot_directive_api` for the templates defined in *TEMPLATE*
and *TEMPLATE_SRCSET*.
+
"""
+from collections import defaultdict
import contextlib
import doctest
from io import StringIO
@@ -182,6 +190,7 @@
from docutils.parsers.rst.directives.images import Image
import jinja2 # Sphinx dependency.
+from sphinx.environment.collectors import EnvironmentCollector
from sphinx.errors import ExtensionError
import matplotlib
@@ -265,6 +274,7 @@ class PlotDirective(Directive):
'scale': directives.nonnegative_int,
'align': Image.align,
'class': directives.class_option,
+ 'filename-prefix': directives.unchanged,
'include-source': _option_boolean,
'show-source-link': _option_boolean,
'format': _option_format,
@@ -312,9 +322,35 @@ def setup(app):
app.connect('build-finished', _copy_css_file)
metadata = {'parallel_read_safe': True, 'parallel_write_safe': True,
'version': matplotlib.__version__}
+ app.connect('builder-inited', init_filename_registry)
+ app.add_env_collector(_FilenameCollector)
return metadata
+# -----------------------------------------------------------------------------
+# Handle Duplicate Filenames
+# -----------------------------------------------------------------------------
+
+def init_filename_registry(app):
+ env = app.builder.env
+ if not hasattr(env, 'mpl_plot_image_basenames'):
+ env.mpl_plot_image_basenames = defaultdict(set)
+
+
+class _FilenameCollector(EnvironmentCollector):
+ def process_doc(self, app, doctree):
+ pass
+
+ def clear_doc(self, app, env, docname):
+ if docname in env.mpl_plot_image_basenames:
+ del env.mpl_plot_image_basenames[docname]
+
+ def merge_other(self, app, env, docnames, other):
+ for docname in other.mpl_plot_image_basenames:
+ env.mpl_plot_image_basenames[docname].update(
+ other.mpl_plot_image_basenames[docname])
+
+
# -----------------------------------------------------------------------------
# Doctest handling
# -----------------------------------------------------------------------------
@@ -600,6 +636,25 @@ def _parse_srcset(entries):
return srcset
+def check_output_base_name(env, output_base):
+ docname = env.docname
+
+ if '.' in output_base or '/' in output_base or '\\' in output_base:
+ raise PlotError(
+ f"The filename-prefix '{output_base}' is invalid. "
+ f"It must not contain dots or slashes.")
+
+ for d in env.mpl_plot_image_basenames:
+ if output_base in env.mpl_plot_image_basenames[d]:
+ if d == docname:
+ raise PlotError(
+ f"The filename-prefix {output_base!r} is used multiple times.")
+ raise PlotError(f"The filename-prefix {output_base!r} is used multiple"
+ f"times (it is also used in {env.doc2path(d)}).")
+
+ env.mpl_plot_image_basenames[docname].add(output_base)
+
+
def render_figures(code, code_path, output_dir, output_base, context,
function_name, config, context_reset=False,
close_figs=False,
@@ -722,7 +777,8 @@ def render_figures(code, code_path, output_dir, output_base, context,
def run(arguments, content, options, state_machine, state, lineno):
document = state_machine.document
- config = document.settings.env.config
+ env = document.settings.env
+ config = env.config
nofigs = 'nofigs' in options
if config.plot_srcset and setup.app.builder.name == 'singlehtml':
@@ -734,6 +790,7 @@ def run(arguments, content, options, state_machine, state, lineno):
options.setdefault('include-source', config.plot_include_source)
options.setdefault('show-source-link', config.plot_html_show_source_link)
+ options.setdefault('filename-prefix', None)
if 'class' in options:
# classes are parsed into a list of string, and output by simply
@@ -775,14 +832,22 @@ def run(arguments, content, options, state_machine, state, lineno):
function_name = None
code = Path(source_file_name).read_text(encoding='utf-8')
- output_base = os.path.basename(source_file_name)
+ if options['filename-prefix']:
+ output_base = options['filename-prefix']
+ check_output_base_name(env, output_base)
+ else:
+ output_base = os.path.basename(source_file_name)
else:
source_file_name = rst_file
code = textwrap.dedent("\n".join(map(str, content)))
- counter = document.attributes.get('_plot_counter', 0) + 1
- document.attributes['_plot_counter'] = counter
- base, ext = os.path.splitext(os.path.basename(source_file_name))
- output_base = '%s-%d.py' % (base, counter)
+ if options['filename-prefix']:
+ output_base = options['filename-prefix']
+ check_output_base_name(env, output_base)
+ else:
+ base, ext = os.path.splitext(os.path.basename(source_file_name))
+ counter = document.attributes.get('_plot_counter', 0) + 1
+ document.attributes['_plot_counter'] = counter
+ output_base = '%s-%d.py' % (base, counter)
function_name = None
caption = options.get('caption', '')
@@ -846,7 +911,7 @@ def run(arguments, content, options, state_machine, state, lineno):
# save script (if necessary)
if options['show-source-link']:
- Path(build_dir, output_base + source_ext).write_text(
+ Path(build_dir, output_base + (source_ext or '.py')).write_text(
doctest.script_from_examples(code)
if source_file_name == rst_file and is_doctest
else code,
@@ -906,7 +971,7 @@ def run(arguments, content, options, state_machine, state, lineno):
# Not-None src_name signals the need for a source download in the
# generated html
if j == 0 and options['show-source-link']:
- src_name = output_base + source_ext
+ src_name = output_base + (source_ext or '.py')
else:
src_name = None
if config.plot_srcset:
diff --git a/lib/matplotlib/spines.py b/lib/matplotlib/spines.py
index 7e77a393f2a2..741491b3dc58 100644
--- a/lib/matplotlib/spines.py
+++ b/lib/matplotlib/spines.py
@@ -232,12 +232,13 @@ def _clear(self):
"""
self._position = None # clear position
- def _adjust_location(self):
- """Automatically set spine bounds to the view interval."""
-
- if self.spine_type == 'circle':
- return
+ def _get_bounds_or_viewLim(self):
+ """
+ Get the bounds of the spine.
+ If self._bounds is None, return self.axes.viewLim.intervalx
+ or self.axes.viewLim.intervaly based on self.spine_type
+ """
if self._bounds is not None:
low, high = self._bounds
elif self.spine_type in ('left', 'right'):
@@ -245,7 +246,16 @@ def _adjust_location(self):
elif self.spine_type in ('top', 'bottom'):
low, high = self.axes.viewLim.intervalx
else:
- raise ValueError(f'unknown spine spine_type: {self.spine_type}')
+ raise ValueError(f'spine_type: {self.spine_type} not supported')
+ return low, high
+
+ def _adjust_location(self):
+ """Automatically set spine bounds to the view interval."""
+
+ if self.spine_type == 'circle':
+ return
+
+ low, high = self._get_bounds_or_viewLim()
if self._patch_type == 'arc':
if self.spine_type in ('bottom', 'top'):
@@ -265,11 +275,17 @@ def _adjust_location(self):
self._path = mpath.Path.arc(np.rad2deg(low), np.rad2deg(high))
if self.spine_type == 'bottom':
- rmin, rmax = self.axes.viewLim.intervaly
+ if self.axis is None:
+ tr = mtransforms.IdentityTransform()
+ else:
+ tr = self.axis.get_transform()
+ rmin, rmax = tr.transform(self.axes.viewLim.intervaly)
try:
rorigin = self.axes.get_rorigin()
except AttributeError:
rorigin = rmin
+ else:
+ rorigin = tr.transform(rorigin)
scaled_diameter = (rmin - rorigin) / (rmax - rorigin)
self._height = scaled_diameter
self._width = scaled_diameter
@@ -418,7 +434,7 @@ def set_bounds(self, low=None, high=None):
'set_bounds() method incompatible with circular spines')
if high is None and np.iterable(low):
low, high = low
- old_low, old_high = self.get_bounds() or (None, None)
+ old_low, old_high = self._get_bounds_or_viewLim()
if low is None:
low = old_low
if high is None:
diff --git a/lib/matplotlib/style/__init__.py b/lib/matplotlib/style/__init__.py
index 488c6d6ae1ec..80c6de00a18d 100644
--- a/lib/matplotlib/style/__init__.py
+++ b/lib/matplotlib/style/__init__.py
@@ -1,4 +1,253 @@
-from .core import available, context, library, reload_library, use
+"""
+Core functions and attributes for the matplotlib style library:
+``use``
+ Select style sheet to override the current matplotlib settings.
+``context``
+ Context manager to use a style sheet temporarily.
+``available``
+ List available style sheets. Underscore-prefixed names are considered private and
+ not listed, though may still be accessed directly from ``library``.
+``library``
+ A dictionary of style names and matplotlib settings.
+"""
-__all__ = ["available", "context", "library", "reload_library", "use"]
+import contextlib
+import importlib.resources
+import logging
+import os
+from pathlib import Path
+import warnings
+
+import matplotlib as mpl
+from matplotlib import _api, _docstring, rc_params_from_file, rcParamsDefault
+
+_log = logging.getLogger(__name__)
+
+__all__ = ['use', 'context', 'available', 'library', 'reload_library']
+
+
+_BASE_LIBRARY_PATH = os.path.join(mpl.get_data_path(), 'stylelib')
+# Users may want multiple library paths, so store a list of paths.
+USER_LIBRARY_PATHS = [os.path.join(mpl.get_configdir(), 'stylelib')]
+_STYLE_EXTENSION = 'mplstyle'
+# A list of rcParams that should not be applied from styles
+_STYLE_BLACKLIST = {
+ 'interactive', 'backend', 'webagg.port', 'webagg.address',
+ 'webagg.port_retries', 'webagg.open_in_browser', 'backend_fallback',
+ 'toolbar', 'timezone', 'figure.max_open_warning',
+ 'figure.raise_window', 'savefig.directory', 'tk.window_focus',
+ 'docstring.hardcopy', 'date.epoch'}
+
+
+@_docstring.Substitution(
+ "\n".join(map("- {}".format, sorted(_STYLE_BLACKLIST, key=str.lower)))
+)
+def use(style):
+ """
+ Use Matplotlib style settings from a style specification.
+
+ The style name of 'default' is reserved for reverting back to
+ the default style settings.
+
+ .. note::
+
+ This updates the `.rcParams` with the settings from the style.
+ `.rcParams` not defined in the style are kept.
+
+ Parameters
+ ----------
+ style : str, dict, Path or list
+
+ A style specification. Valid options are:
+
+ str
+ - One of the style names in `.style.available` (a builtin style or
+ a style installed in the user library path).
+
+ - A dotted name of the form "package.style_name"; in that case,
+ "package" should be an importable Python package name, e.g. at
+ ``/path/to/package/__init__.py``; the loaded style file is
+ ``/path/to/package/style_name.mplstyle``. (Style files in
+ subpackages are likewise supported.)
+
+ - The path or URL to a style file, which gets loaded by
+ `.rc_params_from_file`.
+
+ dict
+ A mapping of key/value pairs for `matplotlib.rcParams`.
+
+ Path
+ The path to a style file, which gets loaded by
+ `.rc_params_from_file`.
+
+ list
+ A list of style specifiers (str, Path or dict), which are applied
+ from first to last in the list.
+
+ Notes
+ -----
+ The following `.rcParams` are not related to style and will be ignored if
+ found in a style specification:
+
+ %s
+ """
+ if isinstance(style, (str, Path)) or hasattr(style, 'keys'):
+ # If name is a single str, Path or dict, make it a single element list.
+ styles = [style]
+ else:
+ styles = style
+
+ style_alias = {'mpl20': 'default', 'mpl15': 'classic'}
+
+ for style in styles:
+ if isinstance(style, str):
+ style = style_alias.get(style, style)
+ if style == "default":
+ # Deprecation warnings were already handled when creating
+ # rcParamsDefault, no need to reemit them here.
+ with _api.suppress_matplotlib_deprecation_warning():
+ # don't trigger RcParams.__getitem__('backend')
+ style = {k: rcParamsDefault[k] for k in rcParamsDefault
+ if k not in _STYLE_BLACKLIST}
+ elif style in library:
+ style = library[style]
+ elif "." in style:
+ pkg, _, name = style.rpartition(".")
+ try:
+ path = importlib.resources.files(pkg) / f"{name}.{_STYLE_EXTENSION}"
+ style = rc_params_from_file(path, use_default_template=False)
+ except (ModuleNotFoundError, OSError, TypeError) as exc:
+ # There is an ambiguity whether a dotted name refers to a
+ # package.style_name or to a dotted file path. Currently,
+ # we silently try the first form and then the second one;
+ # in the future, we may consider forcing file paths to
+ # either use Path objects or be prepended with "./" and use
+ # the slash as marker for file paths.
+ pass
+ if isinstance(style, (str, Path)):
+ try:
+ style = rc_params_from_file(style, use_default_template=False)
+ except OSError as err:
+ raise OSError(
+ f"{style!r} is not a valid package style, path of style "
+ f"file, URL of style file, or library style name (library "
+ f"styles are listed in `style.available`)") from err
+ filtered = {}
+ for k in style: # don't trigger RcParams.__getitem__('backend')
+ if k in _STYLE_BLACKLIST:
+ _api.warn_external(
+ f"Style includes a parameter, {k!r}, that is not "
+ f"related to style. Ignoring this parameter.")
+ else:
+ filtered[k] = style[k]
+ mpl.rcParams.update(filtered)
+
+
+@contextlib.contextmanager
+def context(style, after_reset=False):
+ """
+ Context manager for using style settings temporarily.
+
+ Parameters
+ ----------
+ style : str, dict, Path or list
+ A style specification. Valid options are:
+
+ str
+ - One of the style names in `.style.available` (a builtin style or
+ a style installed in the user library path).
+
+ - A dotted name of the form "package.style_name"; in that case,
+ "package" should be an importable Python package name, e.g. at
+ ``/path/to/package/__init__.py``; the loaded style file is
+ ``/path/to/package/style_name.mplstyle``. (Style files in
+ subpackages are likewise supported.)
+
+ - The path or URL to a style file, which gets loaded by
+ `.rc_params_from_file`.
+ dict
+ A mapping of key/value pairs for `matplotlib.rcParams`.
+
+ Path
+ The path to a style file, which gets loaded by
+ `.rc_params_from_file`.
+
+ list
+ A list of style specifiers (str, Path or dict), which are applied
+ from first to last in the list.
+
+ after_reset : bool
+ If True, apply style after resetting settings to their defaults;
+ otherwise, apply style on top of the current settings.
+ """
+ with mpl.rc_context():
+ if after_reset:
+ mpl.rcdefaults()
+ use(style)
+ yield
+
+
+def _update_user_library(library):
+ """Update style library with user-defined rc files."""
+ for stylelib_path in map(os.path.expanduser, USER_LIBRARY_PATHS):
+ styles = _read_style_directory(stylelib_path)
+ _update_nested_dict(library, styles)
+ return library
+
+
+@_api.deprecated("3.11")
+def update_user_library(library):
+ return _update_user_library(library)
+
+
+def _read_style_directory(style_dir):
+ """Return dictionary of styles defined in *style_dir*."""
+ styles = dict()
+ for path in Path(style_dir).glob(f"*.{_STYLE_EXTENSION}"):
+ with warnings.catch_warnings(record=True) as warns:
+ styles[path.stem] = rc_params_from_file(path, use_default_template=False)
+ for w in warns:
+ _log.warning('In %s: %s', path, w.message)
+ return styles
+
+
+@_api.deprecated("3.11")
+def read_style_directory(style_dir):
+ return _read_style_directory(style_dir)
+
+
+def _update_nested_dict(main_dict, new_dict):
+ """
+ Update nested dict (only level of nesting) with new values.
+
+ Unlike `dict.update`, this assumes that the values of the parent dict are
+ dicts (or dict-like), so you shouldn't replace the nested dict if it
+ already exists. Instead you should update the sub-dict.
+ """
+ # update named styles specified by user
+ for name, rc_dict in new_dict.items():
+ main_dict.setdefault(name, {}).update(rc_dict)
+ return main_dict
+
+
+@_api.deprecated("3.11")
+def update_nested_dict(main_dict, new_dict):
+ return _update_nested_dict(main_dict, new_dict)
+
+
+# Load style library
+# ==================
+_base_library = _read_style_directory(_BASE_LIBRARY_PATH)
+library = {}
+available = []
+
+
+def reload_library():
+ """Reload the style library."""
+ library.clear()
+ library.update(_update_user_library(_base_library.copy()))
+ available[:] = sorted(name for name in library if not name.startswith('_'))
+
+
+reload_library()
diff --git a/lib/matplotlib/style/__init__.pyi b/lib/matplotlib/style/__init__.pyi
new file mode 100644
index 000000000000..c93b504fe6bd
--- /dev/null
+++ b/lib/matplotlib/style/__init__.pyi
@@ -0,0 +1,20 @@
+from collections.abc import Generator
+import contextlib
+
+from matplotlib import RcParams
+from matplotlib.typing import RcStyleType
+
+USER_LIBRARY_PATHS: list[str] = ...
+
+def use(style: RcStyleType) -> None: ...
+@contextlib.contextmanager
+def context(
+ style: RcStyleType, after_reset: bool = ...
+) -> Generator[None, None, None]: ...
+
+library: dict[str, RcParams]
+available: list[str]
+
+def reload_library() -> None: ...
+
+__all__ = ['use', 'context', 'available', 'library', 'reload_library']
diff --git a/lib/matplotlib/style/core.py b/lib/matplotlib/style/core.py
index e36c3c37a882..c377bc64077a 100644
--- a/lib/matplotlib/style/core.py
+++ b/lib/matplotlib/style/core.py
@@ -11,227 +11,17 @@
A dictionary of style names and matplotlib settings.
"""
-import contextlib
-import importlib.resources
-import logging
-import os
-from pathlib import Path
-import warnings
-
-import matplotlib as mpl
-from matplotlib import _api, _docstring, _rc_params_in_file, rcParamsDefault
-
-_log = logging.getLogger(__name__)
-
-__all__ = ['use', 'context', 'available', 'library', 'reload_library']
-
-
-BASE_LIBRARY_PATH = os.path.join(mpl.get_data_path(), 'stylelib')
-# Users may want multiple library paths, so store a list of paths.
-USER_LIBRARY_PATHS = [os.path.join(mpl.get_configdir(), 'stylelib')]
-STYLE_EXTENSION = 'mplstyle'
-# A list of rcParams that should not be applied from styles
-STYLE_BLACKLIST = {
- 'interactive', 'backend', 'webagg.port', 'webagg.address',
- 'webagg.port_retries', 'webagg.open_in_browser', 'backend_fallback',
- 'toolbar', 'timezone', 'figure.max_open_warning',
- 'figure.raise_window', 'savefig.directory', 'tk.window_focus',
- 'docstring.hardcopy', 'date.epoch'}
-
-
-@_docstring.Substitution(
- "\n".join(map("- {}".format, sorted(STYLE_BLACKLIST, key=str.lower)))
+from .. import _api
+from . import (
+ use, context, available, library, reload_library, USER_LIBRARY_PATHS,
+ _BASE_LIBRARY_PATH as BASE_LIBRARY_PATH,
+ _STYLE_EXTENSION as STYLE_EXTENSION,
+ _STYLE_BLACKLIST as STYLE_BLACKLIST,
)
-def use(style):
- """
- Use Matplotlib style settings from a style specification.
-
- The style name of 'default' is reserved for reverting back to
- the default style settings.
-
- .. note::
-
- This updates the `.rcParams` with the settings from the style.
- `.rcParams` not defined in the style are kept.
-
- Parameters
- ----------
- style : str, dict, Path or list
-
- A style specification. Valid options are:
-
- str
- - One of the style names in `.style.available` (a builtin style or
- a style installed in the user library path).
-
- - A dotted name of the form "package.style_name"; in that case,
- "package" should be an importable Python package name, e.g. at
- ``/path/to/package/__init__.py``; the loaded style file is
- ``/path/to/package/style_name.mplstyle``. (Style files in
- subpackages are likewise supported.)
-
- - The path or URL to a style file, which gets loaded by
- `.rc_params_from_file`.
-
- dict
- A mapping of key/value pairs for `matplotlib.rcParams`.
-
- Path
- The path to a style file, which gets loaded by
- `.rc_params_from_file`.
-
- list
- A list of style specifiers (str, Path or dict), which are applied
- from first to last in the list.
-
- Notes
- -----
- The following `.rcParams` are not related to style and will be ignored if
- found in a style specification:
-
- %s
- """
- if isinstance(style, (str, Path)) or hasattr(style, 'keys'):
- # If name is a single str, Path or dict, make it a single element list.
- styles = [style]
- else:
- styles = style
-
- style_alias = {'mpl20': 'default', 'mpl15': 'classic'}
-
- for style in styles:
- if isinstance(style, str):
- style = style_alias.get(style, style)
- if style == "default":
- # Deprecation warnings were already handled when creating
- # rcParamsDefault, no need to reemit them here.
- with _api.suppress_matplotlib_deprecation_warning():
- # don't trigger RcParams.__getitem__('backend')
- style = {k: rcParamsDefault[k] for k in rcParamsDefault
- if k not in STYLE_BLACKLIST}
- elif style in library:
- style = library[style]
- elif "." in style:
- pkg, _, name = style.rpartition(".")
- try:
- path = importlib.resources.files(pkg) / f"{name}.{STYLE_EXTENSION}"
- style = _rc_params_in_file(path)
- except (ModuleNotFoundError, OSError, TypeError) as exc:
- # There is an ambiguity whether a dotted name refers to a
- # package.style_name or to a dotted file path. Currently,
- # we silently try the first form and then the second one;
- # in the future, we may consider forcing file paths to
- # either use Path objects or be prepended with "./" and use
- # the slash as marker for file paths.
- pass
- if isinstance(style, (str, Path)):
- try:
- style = _rc_params_in_file(style)
- except OSError as err:
- raise OSError(
- f"{style!r} is not a valid package style, path of style "
- f"file, URL of style file, or library style name (library "
- f"styles are listed in `style.available`)") from err
- filtered = {}
- for k in style: # don't trigger RcParams.__getitem__('backend')
- if k in STYLE_BLACKLIST:
- _api.warn_external(
- f"Style includes a parameter, {k!r}, that is not "
- f"related to style. Ignoring this parameter.")
- else:
- filtered[k] = style[k]
- mpl.rcParams.update(filtered)
-
-
-@contextlib.contextmanager
-def context(style, after_reset=False):
- """
- Context manager for using style settings temporarily.
-
- Parameters
- ----------
- style : str, dict, Path or list
- A style specification. Valid options are:
-
- str
- - One of the style names in `.style.available` (a builtin style or
- a style installed in the user library path).
-
- - A dotted name of the form "package.style_name"; in that case,
- "package" should be an importable Python package name, e.g. at
- ``/path/to/package/__init__.py``; the loaded style file is
- ``/path/to/package/style_name.mplstyle``. (Style files in
- subpackages are likewise supported.)
-
- - The path or URL to a style file, which gets loaded by
- `.rc_params_from_file`.
- dict
- A mapping of key/value pairs for `matplotlib.rcParams`.
-
- Path
- The path to a style file, which gets loaded by
- `.rc_params_from_file`.
-
- list
- A list of style specifiers (str, Path or dict), which are applied
- from first to last in the list.
-
- after_reset : bool
- If True, apply style after resetting settings to their defaults;
- otherwise, apply style on top of the current settings.
- """
- with mpl.rc_context():
- if after_reset:
- mpl.rcdefaults()
- use(style)
- yield
-
-
-def update_user_library(library):
- """Update style library with user-defined rc files."""
- for stylelib_path in map(os.path.expanduser, USER_LIBRARY_PATHS):
- styles = read_style_directory(stylelib_path)
- update_nested_dict(library, styles)
- return library
-
-
-def read_style_directory(style_dir):
- """Return dictionary of styles defined in *style_dir*."""
- styles = dict()
- for path in Path(style_dir).glob(f"*.{STYLE_EXTENSION}"):
- with warnings.catch_warnings(record=True) as warns:
- styles[path.stem] = _rc_params_in_file(path)
- for w in warns:
- _log.warning('In %s: %s', path, w.message)
- return styles
-
-
-def update_nested_dict(main_dict, new_dict):
- """
- Update nested dict (only level of nesting) with new values.
-
- Unlike `dict.update`, this assumes that the values of the parent dict are
- dicts (or dict-like), so you shouldn't replace the nested dict if it
- already exists. Instead you should update the sub-dict.
- """
- # update named styles specified by user
- for name, rc_dict in new_dict.items():
- main_dict.setdefault(name, {}).update(rc_dict)
- return main_dict
-
-
-# Load style library
-# ==================
-_base_library = read_style_directory(BASE_LIBRARY_PATH)
-library = {}
-available = []
-
-
-def reload_library():
- """Reload the style library."""
- library.clear()
- library.update(update_user_library(_base_library))
- available[:] = sorted(library.keys())
+__all__ = [
+ "use", "context", "available", "library", "reload_library",
+ "USER_LIBRARY_PATHS", "BASE_LIBRARY_PATH", "STYLE_EXTENSION", "STYLE_BLACKLIST",
+]
-reload_library()
+_api.warn_deprecated("3.11", name=__name__, obj_type="module")
diff --git a/lib/matplotlib/style/core.pyi b/lib/matplotlib/style/core.pyi
index 5734b017f7c4..ee21d2f41ef5 100644
--- a/lib/matplotlib/style/core.pyi
+++ b/lib/matplotlib/style/core.pyi
@@ -5,7 +5,9 @@ from matplotlib import RcParams
from matplotlib.typing import RcStyleType
USER_LIBRARY_PATHS: list[str] = ...
+BASE_LIBRARY_PATH: str = ...
STYLE_EXTENSION: str = ...
+STYLE_BLACKLIST: set[str] = ...
def use(style: RcStyleType) -> None: ...
@contextlib.contextmanager
@@ -18,4 +20,7 @@ available: list[str]
def reload_library() -> None: ...
-__all__ = ['use', 'context', 'available', 'library', 'reload_library']
+__all__ = [
+ "use", "context", "available", "library", "reload_library",
+ "USER_LIBRARY_PATHS", "BASE_LIBRARY_PATH", "STYLE_EXTENSION", "STYLE_BLACKLIST",
+]
diff --git a/lib/matplotlib/style/meson.build b/lib/matplotlib/style/meson.build
index 03e7972132bb..e7a183c8581c 100644
--- a/lib/matplotlib/style/meson.build
+++ b/lib/matplotlib/style/meson.build
@@ -4,6 +4,7 @@ python_sources = [
]
typing_sources = [
+ '__init__.pyi',
'core.pyi',
]
diff --git a/lib/matplotlib/testing/__init__.py b/lib/matplotlib/testing/__init__.py
index d6affb1b039f..eff079efe887 100644
--- a/lib/matplotlib/testing/__init__.py
+++ b/lib/matplotlib/testing/__init__.py
@@ -54,7 +54,7 @@ def setup():
def subprocess_run_for_testing(command, env=None, timeout=60, stdout=None,
stderr=None, check=False, text=True,
- capture_output=False):
+ capture_output=False, **kwargs):
"""
Create and run a subprocess.
@@ -97,7 +97,7 @@ def subprocess_run_for_testing(command, env=None, timeout=60, stdout=None,
command, env=env,
timeout=timeout, check=check,
stdout=stdout, stderr=stderr,
- text=text
+ text=text, **kwargs
)
except BlockingIOError:
if sys.platform == "cygwin":
@@ -105,6 +105,16 @@ def subprocess_run_for_testing(command, env=None, timeout=60, stdout=None,
import pytest
pytest.xfail("Fork failure")
raise
+ except subprocess.CalledProcessError as e:
+ if e.stdout:
+ _log.error(f"Subprocess output:\n{e.stdout}")
+ if e.stderr:
+ _log.error(f"Subprocess error:\n{e.stderr}")
+ raise e
+ if proc.stdout:
+ _log.debug(f"Subprocess output:\n{proc.stdout}")
+ if proc.stderr:
+ _log.debug(f"Subprocess error:\n{proc.stderr}")
return proc
diff --git a/lib/matplotlib/testing/__init__.pyi b/lib/matplotlib/testing/__init__.pyi
index 7763cb6a9769..accf973615fa 100644
--- a/lib/matplotlib/testing/__init__.pyi
+++ b/lib/matplotlib/testing/__init__.pyi
@@ -16,6 +16,7 @@ def subprocess_run_for_testing(
*,
text: Literal[True],
capture_output: bool = ...,
+ **kwargs,
) -> subprocess.CompletedProcess[str]: ...
@overload
def subprocess_run_for_testing(
@@ -27,6 +28,7 @@ def subprocess_run_for_testing(
check: bool = ...,
text: Literal[False] = ...,
capture_output: bool = ...,
+ **kwargs,
) -> subprocess.CompletedProcess[bytes]: ...
@overload
def subprocess_run_for_testing(
@@ -38,6 +40,7 @@ def subprocess_run_for_testing(
check: bool = ...,
text: bool = ...,
capture_output: bool = ...,
+ **kwargs,
) -> subprocess.CompletedProcess[bytes] | subprocess.CompletedProcess[str]: ...
def subprocess_run_helper(
func: Callable[[], None],
diff --git a/lib/matplotlib/testing/compare.py b/lib/matplotlib/testing/compare.py
index 67897e76edcb..fa5cd89481b5 100644
--- a/lib/matplotlib/testing/compare.py
+++ b/lib/matplotlib/testing/compare.py
@@ -19,7 +19,7 @@
from PIL import Image
import matplotlib as mpl
-from matplotlib import cbook
+from matplotlib import cbook, _image
from matplotlib.testing.exceptions import ImageComparisonFailure
_log = logging.getLogger(__name__)
@@ -412,7 +412,7 @@ def compare_images(expected, actual, tol, in_decorator=False):
The two given filenames may point to files which are convertible to
PNG via the `!converter` dictionary. The underlying RMS is calculated
- with the `.calculate_rms` function.
+ in a similar way to the `.calculate_rms` function.
Parameters
----------
@@ -483,17 +483,12 @@ def compare_images(expected, actual, tol, in_decorator=False):
if np.array_equal(expected_image, actual_image):
return None
- # convert to signed integers, so that the images can be subtracted without
- # overflow
- expected_image = expected_image.astype(np.int16)
- actual_image = actual_image.astype(np.int16)
-
- rms = calculate_rms(expected_image, actual_image)
+ rms, abs_diff = _image.calculate_rms_and_diff(expected_image, actual_image)
if rms <= tol:
return None
- save_diff_image(expected, actual, diff_image)
+ Image.fromarray(abs_diff).save(diff_image, format="png")
results = dict(rms=rms, expected=str(expected),
actual=str(actual), diff=str(diff_image), tol=tol)
diff --git a/lib/matplotlib/testing/widgets.py b/lib/matplotlib/testing/widgets.py
index 3962567aa7c0..c528ffb2537c 100644
--- a/lib/matplotlib/testing/widgets.py
+++ b/lib/matplotlib/testing/widgets.py
@@ -8,6 +8,8 @@
from unittest import mock
+from matplotlib import _api
+from matplotlib.backend_bases import MouseEvent, KeyEvent
import matplotlib.pyplot as plt
@@ -24,6 +26,7 @@ def noop(*args, **kwargs):
pass
+@_api.deprecated("3.11", alternative="MouseEvent or KeyEvent")
def mock_event(ax, button=1, xdata=0, ydata=0, key=None, step=1):
r"""
Create a mock event that can stand in for `.Event` and its subclasses.
@@ -65,6 +68,7 @@ def mock_event(ax, button=1, xdata=0, ydata=0, key=None, step=1):
return event
+@_api.deprecated("3.11", alternative="callbacks.process(event)")
def do_event(tool, etype, button=1, xdata=0, ydata=0, key=None, step=1):
"""
Trigger an event on the given tool.
@@ -105,15 +109,12 @@ def click_and_drag(tool, start, end, key=None):
An optional key that is pressed during the whole operation
(see also `.KeyEvent`).
"""
- if key is not None:
- # Press key
- do_event(tool, 'on_key_press', xdata=start[0], ydata=start[1],
- button=1, key=key)
+ ax = tool.ax
+ if key is not None: # Press key
+ KeyEvent._from_ax_coords("key_press_event", ax, start, key)._process()
# Click, move, and release mouse
- do_event(tool, 'press', xdata=start[0], ydata=start[1], button=1)
- do_event(tool, 'onmove', xdata=end[0], ydata=end[1], button=1)
- do_event(tool, 'release', xdata=end[0], ydata=end[1], button=1)
- if key is not None:
- # Release key
- do_event(tool, 'on_key_release', xdata=end[0], ydata=end[1],
- button=1, key=key)
+ MouseEvent._from_ax_coords("button_press_event", ax, start, 1)._process()
+ MouseEvent._from_ax_coords("motion_notify_event", ax, end, 1)._process()
+ MouseEvent._from_ax_coords("button_release_event", ax, end, 1)._process()
+ if key is not None: # Release key
+ KeyEvent._from_ax_coords("key_release_event", ax, end, key)._process()
diff --git a/lib/matplotlib/tests/baseline_images/dviread/lualatex.json b/lib/matplotlib/tests/baseline_images/dviread/lualatex.json
new file mode 100644
index 000000000000..8f2d95017ec7
--- /dev/null
+++ b/lib/matplotlib/tests/baseline_images/dviread/lualatex.json
@@ -0,0 +1 @@
+[{"text": [[5046272, 4128768, "A", "lmroman10-regular.otf", 9.96, {}], [5756027, 4128768, "L", "lmroman10-regular.otf", 9.96, {}], [5929697, 4012179, "A", "lmroman7-regular.otf", 6.97, {}], [6218125, 4128768, "T", "lmroman10-regular.otf", 9.96, {}], [6582045, 4269998, "E", "lmroman10-regular.otf", 9.96, {}], [6946425, 4128768, "X", "lmroman10-regular.otf", 9.96, {}], [7656180, 4128768, "d", "DejaVuSans.ttf", 9.96, {"extend": 1.25, "slant": 0.25, "embolden": 0.25}], [8072180, 4128768, "o", "DejaVuSans.ttf", 9.96, {"extend": 1.25, "slant": 0.25, "embolden": 0.25}], [8473140, 4128768, "c", "DejaVuSans.ttf", 9.96, {"extend": 1.25, "slant": 0.25, "embolden": 0.25}], [8833460, 4128768, ".", "DejaVuSans.ttf", 9.96, {"extend": 1.25, "slant": 0.25, "embolden": 0.25}]], "boxes": []}, {"text": [[13686374, 5056284, "\u03c0", "cmmi5.pfb", 4.98, {}], [13716923, 5390308, "2", "cmr5.pfb", 4.98, {}], [13355110, 5463127, "integraldisplay", "cmex10.pfb", 9.96, {}], [13406537, 7324364, "0", "cmr7.pfb", 6.97, {}], [14010471, 5627696, "parenleftBig", "cmex10.pfb", 9.96, {}], [14937513, 5911796, "x", "cmmi10.pfb", 9.96, {}], [14480510, 6804696, "s", "lmroman10-regular.otf", 9.96, {}], [14738721, 6804696, "i", "lmroman10-regular.otf", 9.96, {}], [14920911, 6804696, "n", "lmroman10-regular.otf", 9.96, {}], [15394516, 6804696, "x", "cmmi10.pfb", 9.96, {}], [15847715, 5627696, "parenrightBig", "cmex10.pfb", 9.96, {}], [16239111, 5763501, "2", "cmr7.pfb", 6.97, {}], [16642338, 6355152, "d", "lmroman10-regular.otf", 9.96, {}], [17006718, 6355152, "x", "cmmi10.pfb", 9.96, {}]], "boxes": [[13686374, 5130818, 26213, 284106], [14480510, 6204418, 26213, 1288562]]}]
diff --git a/lib/matplotlib/tests/baseline_images/dviread/pdflatex.json b/lib/matplotlib/tests/baseline_images/dviread/pdflatex.json
new file mode 100644
index 000000000000..4754b722aa58
--- /dev/null
+++ b/lib/matplotlib/tests/baseline_images/dviread/pdflatex.json
@@ -0,0 +1 @@
+[{"text": [[5046272, 4128768, "A", "cmr10.pfb", 9.96, {}], [5756246, 4128768, "L", "cmr10.pfb", 9.96, {}], [5929917, 3994421, "A", "cmr7.pfb", 6.97, {}], [6218464, 4128768, "T", "cmr10.pfb", 9.96, {}], [6582530, 4269852, "E", "cmr10.pfb", 9.96, {}], [6946620, 4128768, "X", "cmr10.pfb", 9.96, {}], [7656594, 4128768, "d", "cmr10.pfb", 9.96, {}], [8020684, 4128768, "o", "cmr10.pfb", 9.96, {}], [8366570, 4128768, "c", "cmr10.pfb", 9.96, {}], [8657841, 4128768, ".", "cmr10.pfb", 9.96, {}]], "boxes": []}, {"text": [[13686591, 5056284, "\u03c0", "cmmi5.pfb", 4.98, {}], [13717140, 5390308, "2", "cmr5.pfb", 4.98, {}], [13355327, 5463127, "integraldisplay", "cmex10.pfb", 9.96, {}], [13406754, 7324364, "0", "cmr7.pfb", 6.97, {}], [14010688, 5627696, "parenleftBig", "cmex10.pfb", 9.96, {}], [14937658, 5911796, "x", "cmmi10.pfb", 9.96, {}], [14480727, 6804696, "s", "cmr10.pfb", 9.96, {}], [14739230, 6804696, "i", "cmr10.pfb", 9.96, {}], [14921275, 6804696, "n", "cmr10.pfb", 9.96, {}], [15394589, 6804696, "x", "cmmi10.pfb", 9.96, {}], [15847788, 5627696, "parenrightBig", "cmex10.pfb", 9.96, {}], [16239184, 5763501, "2", "cmr7.pfb", 6.97, {}], [16642411, 6355152, "d", "cmr10.pfb", 9.96, {}], [17006501, 6355152, "x", "cmmi10.pfb", 9.96, {}]], "boxes": [[13686591, 5130818, 26213, 284106], [14480727, 6204418, 26213, 1288418]]}]
diff --git a/lib/matplotlib/tests/baseline_images/dviread/test.dvi b/lib/matplotlib/tests/baseline_images/dviread/test.dvi
deleted file mode 100644
index 93751ffdcba0..000000000000
Binary files a/lib/matplotlib/tests/baseline_images/dviread/test.dvi and /dev/null differ
diff --git a/lib/matplotlib/tests/baseline_images/dviread/test.json b/lib/matplotlib/tests/baseline_images/dviread/test.json
deleted file mode 100644
index 0809cb9531f1..000000000000
--- a/lib/matplotlib/tests/baseline_images/dviread/test.json
+++ /dev/null
@@ -1,94 +0,0 @@
-[
- {
- "text": [
- [5046272, 4128768, "T", "cmr10", 9.96],
- [5519588, 4128768, "h", "cmr10", 9.96],
- [5883678, 4128768, "i", "cmr10", 9.96],
- [6065723, 4128768, "s", "cmr10", 9.96],
- [6542679, 4128768, "i", "cmr10", 9.96],
- [6724724, 4128768, "s", "cmr10", 9.96],
- [7201680, 4128768, "a", "cmr10", 9.96],
- [7747814, 4128768, "L", "cmr10", 9.96],
- [7921485, 3994421, "A", "cmr7", 6.97],
- [8210032, 4128768, "T", "cmr10", 9.96],
- [8574098, 4269852, "E", "cmr10", 9.96],
- [8938188, 4128768, "X", "cmr10", 9.96],
- [9648162, 4128768, "t", "cmr10", 9.96],
- [9903025, 4128768, "e", "cmr10", 9.96],
- [10194296, 4128768, "s", "cmr10", 9.96],
- [10452799, 4128768, "t", "cmr10", 9.96],
- [10926115, 4128768, "d", "cmr10", 9.96],
- [11290205, 4128768, "o", "cmr10", 9.96],
- [11636091, 4128768, "c", "cmr10", 9.96],
- [11927362, 4128768, "u", "cmr10", 9.96],
- [12291452, 4128768, "m", "cmr10", 9.96],
- [12837587, 4128768, "e", "cmr10", 9.96],
- [13128858, 4128768, "n", "cmr10", 9.96],
- [13474743, 4128768, "t", "cmr10", 9.96],
- [4063232, 4915200, "f", "cmr10", 9.96],
- [4263482, 4915200, "o", "cmr10", 9.96],
- [4591163, 4915200, "r", "cmr10", 9.96],
- [5066299, 4915200, "t", "cmr10", 9.96],
- [5321162, 4915200, "e", "cmr10", 9.96],
- [5612433, 4915200, "s", "cmr10", 9.96],
- [5870936, 4915200, "t", "cmr10", 9.96],
- [6125799, 4915200, "i", "cmr10", 9.96],
- [6307844, 4915200, "n", "cmr10", 9.96],
- [6671934, 4915200, "g", "cmr10", 9.96],
- [7218068, 4915200, "m", "cmr10", 9.96],
- [7764203, 4915200, "a", "cmr10", 9.96],
- [8091884, 4915200, "t", "cmr10", 9.96],
- [8346747, 4915200, "p", "cmr10", 9.96],
- [8710837, 4915200, "l", "cmr10", 9.96],
- [8892882, 4915200, "o", "cmr10", 9.96],
- [9220563, 4915200, "t", "cmr10", 9.96],
- [9475426, 4915200, "l", "cmr10", 9.96],
- [9657471, 4915200, "i", "cmr10", 9.96],
- [9839516, 4915200, "b", "cmr10", 9.96],
- [10203606, 4915200, "'", "cmr10", 9.96],
- [10385651, 4915200, "s", "cmr10", 9.96],
- [10862607, 4915200, "d", "cmr10", 9.96],
- [11226697, 4915200, "v", "cmr10", 9.96],
- [11572583, 4915200, "i", "cmr10", 9.96],
- [11754628, 4915200, "r", "cmr10", 9.96],
- [12011311, 4915200, "e", "cmr10", 9.96],
- [12302582, 4915200, "a", "cmr10", 9.96],
- [12630263, 4915200, "d", "cmr10", 9.96],
- [13686591, 6629148, "\u0019", "cmmi5", 4.98],
- [13717140, 6963172, "2", "cmr5", 4.98],
- [13355327, 7035991, "Z", "cmex10", 9.96],
- [13406754, 8897228, "0", "cmr7", 6.97],
- [14010688, 7200560, "\u0010", "cmex10", 9.96],
- [14937658, 7484660, "x", "cmmi10", 9.96],
- [14480727, 8377560, "s", "cmr10", 9.96],
- [14739230, 8377560, "i", "cmr10", 9.96],
- [14921275, 8377560, "n", "cmr10", 9.96],
- [15394589, 8377560, "x", "cmmi10", 9.96],
- [15847788, 7200560, "\u0011", "cmex10", 9.96],
- [16239184, 7336365, "2", "cmr7", 6.97],
- [16642411, 7928016, "d", "cmr10", 9.96],
- [17006501, 7928016, "x", "cmmi10", 9.96]
- ],
- "boxes": [
- [4063232, 5701632, 65536, 22609920],
- [13686591, 6703682, 26213, 284106],
- [14480727, 7777282, 26213, 1288418]
- ]
- },
- {
- "text": [
- [5046272, 4128768, "a", "cmr10", 9.96],
- [5373953, 4128768, "n", "cmr10", 9.96],
- [5738043, 4128768, "o", "cmr10", 9.96],
- [6065724, 4128768, "t", "cmr10", 9.96],
- [6320587, 4128768, "h", "cmr10", 9.96],
- [6684677, 4128768, "e", "cmr10", 9.96],
- [6975948, 4128768, "r", "cmr10", 9.96],
- [7451084, 4128768, "p", "cmr10", 9.96],
- [7815174, 4128768, "a", "cmr10", 9.96],
- [8142855, 4128768, "g", "cmr10", 9.96],
- [8470536, 4128768, "e", "cmr10", 9.96]
- ],
- "boxes": []
- }
-]
diff --git a/lib/matplotlib/tests/baseline_images/dviread/test.tex b/lib/matplotlib/tests/baseline_images/dviread/test.tex
index 33220fedae3e..4a2d4720c065 100644
--- a/lib/matplotlib/tests/baseline_images/dviread/test.tex
+++ b/lib/matplotlib/tests/baseline_images/dviread/test.tex
@@ -1,17 +1,19 @@
-% source file for test.dvi
\documentclass{article}
+\usepackage{iftex}
+\iftutex\usepackage{fontspec}\fi % xetex or luatex
\pagestyle{empty}
+
\begin{document}
-This is a \LaTeX\ test document\\
-for testing matplotlib's dviread
+A \LaTeX {
+ \iftutex\fontspec{DejaVuSans.ttf}[
+ FakeSlant=0.25, FakeStretch=1.25, FakeBold=2.5, Color=0000FF]\fi
+ doc.
+}
-\noindent\rule{\textwidth}{1pt}
+\newpage
\[ \int\limits_0^{\frac{\pi}{2}} \Bigl(\frac{x}{\sin x}\Bigr)^2\,\mathrm{d}x \]
\special{Special!}
-\newpage
-another page
-
\end{document}
diff --git a/lib/matplotlib/tests/baseline_images/dviread/xelatex.json b/lib/matplotlib/tests/baseline_images/dviread/xelatex.json
new file mode 100644
index 000000000000..8fb81ddf0c7e
--- /dev/null
+++ b/lib/matplotlib/tests/baseline_images/dviread/xelatex.json
@@ -0,0 +1 @@
+[{"text": [[5046272, 4128768, "A", "lmroman10-regular.otf", 9.96, {}], [5756027, 4128768, "L", "lmroman10-regular.otf", 9.96, {}], [5929697, 4012179, "A", "lmroman7-regular.otf", 6.97, {}], [6218125, 4128768, "T", "lmroman10-regular.otf", 9.96, {}], [6582045, 4269998, "E", "lmroman10-regular.otf", 9.96, {}], [6946425, 4128768, "X", "lmroman10-regular.otf", 9.96, {}], [7656180, 4128768, "d", "DejaVuSans.ttf", 9.96, {"rgba": [0, 0, 255, 255], "extend": 1.25, "slant": 0.25, "embolden": 0.25}], [8176180, 4128768, "o", "DejaVuSans.ttf", 9.96, {"rgba": [0, 0, 255, 255], "extend": 1.25, "slant": 0.25, "embolden": 0.25}], [8677380, 4128768, "c", "DejaVuSans.ttf", 9.96, {"rgba": [0, 0, 255, 255], "extend": 1.25, "slant": 0.25, "embolden": 0.25}], [9127780, 4128768, ".", "DejaVuSans.ttf", 9.96, {"rgba": [0, 0, 255, 255], "extend": 1.25, "slant": 0.25, "embolden": 0.25}]], "boxes": []}, {"text": [[13686374, 5056284, "\u03c0", "cmmi5.pfb", 4.98, {}], [13716923, 5390308, "2", "cmr5.pfb", 4.98, {}], [13355110, 5463127, "integraldisplay", "cmex10.pfb", 9.96, {}], [13406537, 7324364, "0", "cmr7.pfb", 6.97, {}], [14010471, 5627696, "parenleftBig", "cmex10.pfb", 9.96, {}], [14937513, 5911796, "x", "cmmi10.pfb", 9.96, {}], [14480510, 6804696, "s", "lmroman10-regular.otf", 9.96, {}], [14738722, 6804696, "i", "lmroman10-regular.otf", 9.96, {}], [14920912, 6804696, "n", "lmroman10-regular.otf", 9.96, {}], [15394516, 6804696, "x", "cmmi10.pfb", 9.96, {}], [15847715, 5627696, "parenrightBig", "cmex10.pfb", 9.96, {}], [16239111, 5763501, "2", "cmr7.pfb", 6.97, {}], [16642338, 6355152, "d", "lmroman10-regular.otf", 9.96, {}], [17006718, 6355152, "x", "cmmi10.pfb", 9.96, {}]], "boxes": [[13686374, 5130818, 26213, 284106], [14480510, 6204418, 26213, 1288562]]}]
diff --git a/lib/matplotlib/tests/baseline_images/test_colors/test_norm_abc.png b/lib/matplotlib/tests/baseline_images/test_colors/test_norm_abc.png
new file mode 100644
index 000000000000..077365674ac2
Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_colors/test_norm_abc.png differ
diff --git a/lib/matplotlib/tests/baseline_images/test_image/log_scale_image.pdf b/lib/matplotlib/tests/baseline_images/test_image/log_scale_image.pdf
index b338fce6ee5a..c26419850251 100644
Binary files a/lib/matplotlib/tests/baseline_images/test_image/log_scale_image.pdf and b/lib/matplotlib/tests/baseline_images/test_image/log_scale_image.pdf differ
diff --git a/lib/matplotlib/tests/baseline_images/test_image/log_scale_image.png b/lib/matplotlib/tests/baseline_images/test_image/log_scale_image.png
index 9d93c8fb00bf..1df80c1b2045 100644
Binary files a/lib/matplotlib/tests/baseline_images/test_image/log_scale_image.png and b/lib/matplotlib/tests/baseline_images/test_image/log_scale_image.png differ
diff --git a/lib/matplotlib/tests/baseline_images/test_image/log_scale_image.svg b/lib/matplotlib/tests/baseline_images/test_image/log_scale_image.svg
index 6c958cc79592..259eb2c9c7f3 100644
--- a/lib/matplotlib/tests/baseline_images/test_image/log_scale_image.svg
+++ b/lib/matplotlib/tests/baseline_images/test_image/log_scale_image.svg
@@ -1,12 +1,23 @@
-
-