diff --git a/.appveyor.yml b/.appveyor.yml index 13705adc99f9..4521bc876a8f 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -29,16 +29,12 @@ environment: matrix: - PYTHON_VERSION: "3.11" - TEST_ALL: "yes" # We always use a 64-bit machine, but can build x86 distributions # with the PYTHON_ARCH variable platform: - x64 -# all our python builds have to happen in tests_script... -build: false - cache: - '%LOCALAPPDATA%\pip\Cache' - '%USERPROFILE%\.cache\matplotlib' @@ -57,10 +53,18 @@ init: - micromamba info install: - - micromamba env create -f environment.yml python=%PYTHON_VERSION% pywin32 + - set EXTRA_PACKAGES=pywin32 codecov + # These are optional dependencies so that we don't skip so many tests... + - set EXTRA_PACKAGES=%EXTRA_PACKAGES% ffmpeg inkscape + # miktex is available on conda, but seems to fail with permission errors. + # missing packages on conda-forge for imagemagick + # This install sometimes failed randomly :-( + # - choco install imagemagick + + - micromamba env create -f environment.yml python=%PYTHON_VERSION% %EXTRA_PACKAGES% - micromamba activate mpl-dev -test_script: +build_script: # Now build the thing.. - set LINK=/LIBPATH:%cd%\lib - pip install -v --no-build-isolation --editable .[dev] @@ -68,13 +72,7 @@ test_script: - set "DUMPBIN=%VS140COMNTOOLS%\..\..\VC\bin\dumpbin.exe" - '"%DUMPBIN%" /DEPENDENTS lib\matplotlib\ft2font*.pyd | findstr freetype.*.dll && exit /b 1 || exit /b 0' - # this are optional dependencies so that we don't skip so many tests... - - if x%TEST_ALL% == xyes micromamba install -q ffmpeg inkscape - # miktex is available on conda, but seems to fail with permission errors. - # missing packages on conda-forge for imagemagick - # This install sometimes failed randomly :-( - # - choco install imagemagick - +test_script: # Test import of tkagg backend - python -c "import matplotlib as m; m.use('tkagg'); @@ -90,7 +88,6 @@ artifacts: type: Zip on_finish: - - micromamba install codecov - codecov -e PYTHON_VERSION PLATFORM -n "%PYTHON_VERSION% Windows" on_failure: diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 9a7b40ccac10..04609e6a868f 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -34,7 +34,7 @@ jobs: persist-credentials: false - name: Initialize CodeQL - uses: github/codeql-action/init@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4.35.2 + uses: github/codeql-action/init@9e0d7b8d25671d64c341c19c0152d693099fb5ba # v4.35.5 with: languages: ${{ matrix.language }} @@ -45,4 +45,4 @@ jobs: pip install --user -v . - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4.35.2 + uses: github/codeql-action/analyze@9e0d7b8d25671d64c341c19c0152d693099fb5ba # v4.35.5 diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml index 2914c64a8461..600e7fc34a95 100644 --- a/.github/workflows/labeler.yml +++ b/.github/workflows/labeler.yml @@ -12,6 +12,6 @@ jobs: pull-requests: write runs-on: ubuntu-latest steps: - - uses: actions/labeler@634933edcd8ababfe52f92936142cc22ac488b1b # v6.0.1 + - uses: actions/labeler@f27b608878404679385c85cfa523b85ccb86e213 # v6.1.0 with: sync-labels: true diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml index f6af70b8b233..51593f607653 100644 --- a/.github/workflows/linting.yml +++ b/.github/workflows/linting.yml @@ -18,7 +18,7 @@ jobs: - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: python-version: "3.x" - - uses: j178/prek-action@cbc2f23eb5539cf20d82d1aabd0d0ecbcc56f4e3 # v2.0.2 + - uses: j178/prek-action@bdca6f102f98e2b4c7029491a53dfd366469e33d # v2.0.4 with: extra-args: --hook-stage manual --all-files diff --git a/.github/workflows/pr_welcome.yml b/.github/workflows/pr_welcome.yml index 48691e61d87b..70388a3507b1 100644 --- a/.github/workflows/pr_welcome.yml +++ b/.github/workflows/pr_welcome.yml @@ -16,7 +16,7 @@ jobs: issues: write pull-requests: write steps: - - uses: plbstl/first-contribution@7c31f41b0e7a70adfcae06cf964679f61af6780b # v4.3.0 + - uses: plbstl/first-contribution@4fb1541ce2706255850d56c5684552607be1ae9b # v4.2.0 with: labels: first-contribution pr-opened-msg: >+ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index bfce3c54a030..478d97e59588 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -44,7 +44,7 @@ repos: pass_filenames: false - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: d1b833175a5d08a925900115526febd8fe71c98e # frozen: v0.15.11 + rev: 6fec9b7edb08fd9989088709d864a7826dc74e80 # frozen: v0.15.12 hooks: # Run the linter. - id: ruff-check @@ -70,8 +70,8 @@ repos: hooks: - id: rstcheck additional_dependencies: + - rstcheck-core!=1.3 # https://github.com/rstcheck/rstcheck-core/pull/114#pullrequestreview-4239740896 - sphinx>=1.8.1 - - tomli - repo: https://github.com/adrienverge/yamllint rev: cba56bcde1fdd01c1deb3f945e69764c291a6530 # frozen: v1.38.0 hooks: diff --git a/LICENSE/LICENSE_JSXTOOLS_RESIZE_OBSERVER b/LICENSE/LICENSE_JSXTOOLS_RESIZE_OBSERVER deleted file mode 100644 index 0bc1fa7060b7..000000000000 --- a/LICENSE/LICENSE_JSXTOOLS_RESIZE_OBSERVER +++ /dev/null @@ -1,108 +0,0 @@ -# CC0 1.0 Universal - -## Statement of Purpose - -The laws of most jurisdictions throughout the world automatically confer -exclusive Copyright and Related Rights (defined below) upon the creator and -subsequent owner(s) (each and all, an “owner”) of an original work of -authorship and/or a database (each, a “Work”). - -Certain owners wish to permanently relinquish those rights to a Work for the -purpose of contributing to a commons of creative, cultural and scientific works -(“Commons”) that the public can reliably and without fear of later claims of -infringement build upon, modify, incorporate in other works, reuse and -redistribute as freely as possible in any form whatsoever and for any purposes, -including without limitation commercial purposes. These owners may contribute -to the Commons to promote the ideal of a free culture and the further -production of creative, cultural and scientific works, or to gain reputation or -greater distribution for their Work in part through the use and efforts of -others. - -For these and/or other purposes and motivations, and without any expectation of -additional consideration or compensation, the person associating CC0 with a -Work (the “Affirmer”), to the extent that he or she is an owner of Copyright -and Related Rights in the Work, voluntarily elects to apply CC0 to the Work and -publicly distribute the Work under its terms, with knowledge of his or her -Copyright and Related Rights in the Work and the meaning and intended legal -effect of CC0 on those rights. - -1. Copyright and Related Rights. A Work made available under CC0 may be - protected by copyright and related or neighboring rights (“Copyright and - Related Rights”). Copyright and Related Rights include, but are not limited - to, the following: - 1. the right to reproduce, adapt, distribute, perform, display, communicate, - and translate a Work; - 2. moral rights retained by the original author(s) and/or performer(s); - 3. publicity and privacy rights pertaining to a person’s image or likeness - depicted in a Work; - 4. rights protecting against unfair competition in regards to a Work, - subject to the limitations in paragraph 4(i), below; - 5. rights protecting the extraction, dissemination, use and reuse of data in - a Work; - 6. database rights (such as those arising under Directive 96/9/EC of the - European Parliament and of the Council of 11 March 1996 on the legal - protection of databases, and under any national implementation thereof, - including any amended or successor version of such directive); and - 7. other similar, equivalent or corresponding rights throughout the world - based on applicable law or treaty, and any national implementations - thereof. - -2. Waiver. To the greatest extent permitted by, but not in contravention of, - applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and - unconditionally waives, abandons, and surrenders all of Affirmer’s Copyright - and Related Rights and associated claims and causes of action, whether now - known or unknown (including existing as well as future claims and causes of - action), in the Work (i) in all territories worldwide, (ii) for the maximum - duration provided by applicable law or treaty (including future time - extensions), (iii) in any current or future medium and for any number of - copies, and (iv) for any purpose whatsoever, including without limitation - commercial, advertising or promotional purposes (the “Waiver”). Affirmer - makes the Waiver for the benefit of each member of the public at large and - to the detriment of Affirmer’s heirs and successors, fully intending that - such Waiver shall not be subject to revocation, rescission, cancellation, - termination, or any other legal or equitable action to disrupt the quiet - enjoyment of the Work by the public as contemplated by Affirmer’s express - Statement of Purpose. - -3. Public License Fallback. Should any part of the Waiver for any reason be - judged legally invalid or ineffective under applicable law, then the Waiver - shall be preserved to the maximum extent permitted taking into account - Affirmer’s express Statement of Purpose. In addition, to the extent the - Waiver is so judged Affirmer hereby grants to each affected person a - royalty-free, non transferable, non sublicensable, non exclusive, - irrevocable and unconditional license to exercise Affirmer’s Copyright and - Related Rights in the Work (i) in all territories worldwide, (ii) for the - maximum duration provided by applicable law or treaty (including future time - extensions), (iii) in any current or future medium and for any number of - copies, and (iv) for any purpose whatsoever, including without limitation - commercial, advertising or promotional purposes (the “License”). The License - shall be deemed effective as of the date CC0 was applied by Affirmer to the - Work. Should any part of the License for any reason be judged legally - invalid or ineffective under applicable law, such partial invalidity or - ineffectiveness shall not invalidate the remainder of the License, and in - such case Affirmer hereby affirms that he or she will not (i) exercise any - of his or her remaining Copyright and Related Rights in the Work or (ii) - assert any associated claims and causes of action with respect to the Work, - in either case contrary to Affirmer’s express Statement of Purpose. - -4. Limitations and Disclaimers. - 1. No trademark or patent rights held by Affirmer are waived, abandoned, - surrendered, licensed or otherwise affected by this document. - 2. Affirmer offers the Work as-is and makes no representations or warranties - of any kind concerning the Work, express, implied, statutory or - otherwise, including without limitation warranties of title, - merchantability, fitness for a particular purpose, non infringement, or - the absence of latent or other defects, accuracy, or the present or - absence of errors, whether or not discoverable, all to the greatest - extent permissible under applicable law. - 3. Affirmer disclaims responsibility for clearing rights of other persons - that may apply to the Work or any use thereof, including without - limitation any person’s Copyright and Related Rights in the Work. - Further, Affirmer disclaims responsibility for obtaining any necessary - consents, permissions or other rights required for any use of the Work. - 4. Affirmer understands and acknowledges that Creative Commons is not a - party to this document and has no duty or obligation with respect to this - CC0 or use of the Work. - -For more information, please see -http://creativecommons.org/publicdomain/zero/1.0/. diff --git a/doc/_static/image.svg b/doc/_static/image.svg new file mode 100644 index 000000000000..c101e6aea399 --- /dev/null +++ b/doc/_static/image.svg @@ -0,0 +1,381 @@ + + + + + + + + 2026-04-26T10:16:40.198456 + image/svg+xml + + + Matplotlib v3.11.0.dev2285+ge1329a5eb.d20260419, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/_static/scatter.svg b/doc/_static/scatter.svg new file mode 100644 index 000000000000..6db3c159c093 --- /dev/null +++ b/doc/_static/scatter.svg @@ -0,0 +1,591 @@ + + + + + + + + 2026-04-26T10:16:39.958872 + image/svg+xml + + + Matplotlib v3.11.0.dev2285+ge1329a5eb.d20260419, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/api/.gitignore b/doc/api/.gitignore deleted file mode 100644 index dbed88d89836..000000000000 --- a/doc/api/.gitignore +++ /dev/null @@ -1 +0,0 @@ -scalarmappable.gen_rst diff --git a/doc/api/cm_api.rst b/doc/api/cm_api.rst index c9509389a2bb..8476ab14cb86 100644 --- a/doc/api/cm_api.rst +++ b/doc/api/cm_api.rst @@ -7,4 +7,20 @@ :undoc-members: :show-inheritance: -.. include:: scalarmappable.gen_rst +.. class:: ScalarMappable(colorizer, **kwargs) + :canonical: matplotlib.colorizer._ScalarMappable + + .. automethod:: autoscale + .. automethod:: autoscale_None + .. automethod:: changed + .. autoproperty:: colorbar + .. automethod:: get_alpha + .. automethod:: get_array + .. automethod:: get_clim + .. automethod:: get_cmap + .. autoproperty:: norm + .. automethod:: set_array + .. automethod:: set_clim + .. automethod:: set_cmap + .. automethod:: set_norm + .. automethod:: to_rgba diff --git a/doc/api/next_api_changes/behavior/30108-REC.rst b/doc/api/next_api_changes/behavior/30108-REC.rst new file mode 100644 index 000000000000..ce4fb0833207 --- /dev/null +++ b/doc/api/next_api_changes/behavior/30108-REC.rst @@ -0,0 +1,6 @@ +Complex layouts and constrained layout +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Constrained layout now produces smaller spacing between subplots in some +circumstances. This should only affect complex layouts where rows or columns +contain different numbers of subplots, for example a layout created with +``plt.subplot_mosaic('AC;BC', layout='constrained')``. diff --git a/doc/api/next_api_changes/behavior/31578-TH.rst b/doc/api/next_api_changes/behavior/31578-TH.rst new file mode 100644 index 000000000000..0607652c7c8f --- /dev/null +++ b/doc/api/next_api_changes/behavior/31578-TH.rst @@ -0,0 +1,10 @@ +SVG links open in new tab or window +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +`.Artist.set_url` allows to turn the Artist into a link. In SVG output, +this has been implemented without specifying a `target`_ attribute. The default +target value "_self" resulted in replacing the SVG document with the linked page +when the link was clicked. + +The target is now set to "_blank" so that the link opens in a new tab or window. + +.. _target: https://developer.mozilla.org/en-US/docs/Web/SVG/Reference/Attribute/target diff --git a/doc/api/next_api_changes/deprecations/29152_REC.rst b/doc/api/next_api_changes/deprecations/29152_REC.rst new file mode 100644 index 000000000000..cedc91e81410 --- /dev/null +++ b/doc/api/next_api_changes/deprecations/29152_REC.rst @@ -0,0 +1,13 @@ +``pie`` *labels* and *labeldistance* parameters +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Currently the *labels* parameter of `~.Axes.pie` is used both for annotating the +pie wedges directly, and for automatic legend entries. For consistency +with other plotting methods, in future *labels* will only be used for the legend. + +The *labeldistance* parameter will therefore default to ``None`` from Matplotlib +3.14, when it will also be deprecated and then removed in Matplotlib 3.16. To +preserve the existing behavior for now, set ``labeldistance=1.1``. For the longer +term, to place labels on the wedges use the new *wedge_labels* and +*wedge_label_distance* parameters of `~.Axes.pie` or the `~.Axes.pie_label` method. +Note that `~.Axes.pie_label` allows for more customization of the label positions via +the *rotate* and *alignment* parameters as well as *distance*. diff --git a/doc/api/next_api_changes/deprecations/31630-ES.rst b/doc/api/next_api_changes/deprecations/31630-ES.rst new file mode 100644 index 000000000000..2509b4323022 --- /dev/null +++ b/doc/api/next_api_changes/deprecations/31630-ES.rst @@ -0,0 +1,10 @@ +``apply_theta_transforms`` option in ``PolarTransform`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Applying theta transforms in `~matplotlib.projections.polar.PolarTransform` and +`~matplotlib.projections.polar.InvertedPolarTransform` has been removed, and the +*apply_theta_transforms* keyword argument is deprecated for both classes. + +If you need to retain the behaviour where theta values are transformed, chain the +``PolarTransform`` with a `~matplotlib.transforms.Affine2D` transform that performs the +theta shift and/or sign shift. diff --git a/doc/api/next_api_changes/removals/30004-DS.rst b/doc/api/next_api_changes/removals/30004-DS.rst deleted file mode 100644 index f5fdf214366c..000000000000 --- a/doc/api/next_api_changes/removals/30004-DS.rst +++ /dev/null @@ -1,10 +0,0 @@ -``apply_theta_transforms`` option in ``PolarTransform`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Applying theta transforms in `~matplotlib.projections.polar.PolarTransform` and -`~matplotlib.projections.polar.InvertedPolarTransform` has been removed, and -the ``apply_theta_transforms`` keyword argument removed from both classes. - -If you need to retain the behaviour where theta values -are transformed, chain the ``PolarTransform`` with a `~matplotlib.transforms.Affine2D` -transform that performs the theta shift and/or sign shift. diff --git a/doc/api/next_api_changes/removals/31588-ES.rst b/doc/api/next_api_changes/removals/31588-ES.rst new file mode 100644 index 000000000000..8709c5a77f5f --- /dev/null +++ b/doc/api/next_api_changes/removals/31588-ES.rst @@ -0,0 +1,18 @@ +``boxplot`` tick labels +^^^^^^^^^^^^^^^^^^^^^^^ + +The parameter *labels* has been removed in favour of *tick_labels* for clarity and +consistency with `~.Axes.bar`. + +Image path semantics of toolmanager-based tools +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Previously, MEP22 ("toolmanager-based") Tools would try to load their icon +(``tool.image``) relative to the current working directory, or, as a fallback, from +Matplotlib's own image directory. Because both approaches are problematic for +third-party tools (the end-user may change the current working directory at any time, +and third-parties cannot add new icons in Matplotlib's image directory), this behavior +has been removed; instead, ``tool.image`` is now interpreted relative to the directory +containing the source file where the ``Tool.image`` class attribute is defined. +(Defining ``tool.image`` as an absolute path also works and is compatible with both the +old and the new semantics.) diff --git a/doc/conf.py b/doc/conf.py index 4be3fcf3afee..6651383fcacb 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -837,58 +837,6 @@ def linkcode_resolve(domain, info): extensions.append('sphinx.ext.viewcode') -def generate_ScalarMappable_docs(): - - import matplotlib.colorizer - from numpydoc.docscrape_sphinx import get_doc_object - from pathlib import Path - import textwrap - from sphinx.util.inspect import stringify_signature - target_file = Path(__file__).parent / 'api' / 'scalarmappable.gen_rst' - with open(target_file, 'w') as fout: - fout.write(""" -.. class:: ScalarMappable(colorizer, **kwargs) - :canonical: matplotlib.colorizer._ScalarMappable - -""") - for meth in [ - matplotlib.colorizer._ScalarMappable.autoscale, - matplotlib.colorizer._ScalarMappable.autoscale_None, - matplotlib.colorizer._ScalarMappable.changed, - """ - .. attribute:: colorbar - - The last colorbar associated with this ScalarMappable. May be None. -""", - matplotlib.colorizer._ScalarMappable.get_alpha, - matplotlib.colorizer._ScalarMappable.get_array, - matplotlib.colorizer._ScalarMappable.get_clim, - matplotlib.colorizer._ScalarMappable.get_cmap, - """ - .. property:: norm -""", - matplotlib.colorizer._ScalarMappable.set_array, - matplotlib.colorizer._ScalarMappable.set_clim, - matplotlib.colorizer._ScalarMappable.set_cmap, - matplotlib.colorizer._ScalarMappable.set_norm, - matplotlib.colorizer._ScalarMappable.to_rgba, - ]: - if isinstance(meth, str): - fout.write(meth) - else: - name = meth.__name__ - sig = stringify_signature(inspect.signature(meth)) - docstring = textwrap.indent( - str(get_doc_object(meth)), - ' ' - ).rstrip() - fout.write(f""" - .. method:: {name}{sig} -{docstring} - -""") - - # ----------------------------------------------------------------------------- # Sphinx setup # ----------------------------------------------------------------------------- @@ -902,5 +850,4 @@ def setup(app): app.connect('autodoc-process-bases', autodoc_process_bases) if sphinx.version_info[:2] < (7, 1): app.connect('html-page-context', add_html_cache_busting, priority=1000) - generate_ScalarMappable_docs() app.config.autodoc_use_legacy_class_based = True diff --git a/doc/devel/coding_guide.rst b/doc/devel/coding_guide.rst index fe7769909368..7a4b296b52ce 100644 --- a/doc/devel/coding_guide.rst +++ b/doc/devel/coding_guide.rst @@ -190,8 +190,8 @@ local arguments and the rest are passed on as .. _using_logging: -Using logging for debug messages -================================ +Use logging for debug messages +============================== Matplotlib uses the standard Python `logging` library to write verbose warnings, information, and debug messages. Please use it! In all those places diff --git a/doc/devel/document.rst b/doc/devel/document.rst index bea4771af383..a4a4926fdd95 100644 --- a/doc/devel/document.rst +++ b/doc/devel/document.rst @@ -154,8 +154,8 @@ for opening them in your default browser is: .. _writing-rest-pages: -Write ReST pages -================ +reStructuredText pages +====================== Most documentation is either in the docstrings of individual classes and methods, in explicit ``.rst`` files, or in examples and tutorials. @@ -243,11 +243,15 @@ nor the ````literal```` role: Do not describe ``argument`` like this. -Write mathematical expressions ------------------------------- +Mathematical expressions +------------------------ +Use sphinx's built in math support: + +- **Inline math:** Use the ``:math:`` + `role `__ +- **Math blocks:** Use the ``.. math::`` + `directive `__ -In most cases, you will likely want to use one of `Sphinx's builtin Math -extensions `__. In rare cases we want the rendering of the mathematical text in the documentation html to exactly match with the rendering of the mathematical expression in the Matplotlib figure. In these cases, you can use the @@ -257,17 +261,17 @@ expression in the Matplotlib figure. In these cases, you can use the .. _internal-section-refs: -Refer to other documents and sections -------------------------------------- +Cross-references +---------------- Sphinx_ supports internal references_: -========== =============== =========================================== -Role Links target Representation in rendered HTML -========== =============== =========================================== -|doc-dir|_ document link to a page -|ref-dir|_ reference label link to an anchor associated with a heading -========== =============== =========================================== +========== ============================== =========================================== +Role Link target Representation in rendered HTML +========== ============================== =========================================== +|doc-dir|_ :ref:`page ` link to a page +|ref-dir|_ :ref:`section ` link to an anchor associated with a heading +========== ============================== =========================================== .. The following is a hack to have a link with literal formatting See https://stackoverflow.com/a/4836544 @@ -277,63 +281,53 @@ Role Links target Representation in rendered HTML .. |ref-dir| replace:: ``:ref:`` .. _ref-dir: https://www.sphinx-doc.org/en/master/usage/restructuredtext/roles.html#role-ref -Examples: +.. _link-pages: -.. code-block:: rst +Link to pages +^^^^^^^^^^^^^ - See the :doc:`/install/index` +To cross-link to another page, use the ``:doc:`` role. We generally prefer +absolute paths, starting with ``/`` as the :file:`doc` root directory. + +Example: - See the tutorial :ref:`quick_start` +.. code-block:: rst - See the example :doc:`/gallery/lines_bars_and_markers/simple_plot` + See the :doc:`/install/index` will render as: See the :doc:`/install/index` - See the tutorial :ref:`quick_start` - - See the example :doc:`/gallery/lines_bars_and_markers/simple_plot` +.. _link-sections: -Sections can also be given reference labels. For instance from the -:doc:`/install/index` link: - -.. code-block:: rst - - .. _clean-install: - - How to completely remove Matplotlib - =================================== +Link to sections +^^^^^^^^^^^^^^^^ - Occasionally, problems with Matplotlib can be solved with a clean... +Use hyphen-separated, descriptive names for reference labels. +Do not encode the documentation hierarchy in the label as that may change; +e.g. do not prefix all *User guide* labels with ``user-``. -and refer to it using the standard reference syntax: +To cross-link a specific section, add a reference label ``.. _label-name:`` +before the section .. code-block:: rst - See :ref:`clean-install` + .. _pr-author-guidelines: -will give the following link: :ref:`clean-install` + Summary for pull request authors + ================================ -To maximize internal consistency in section labeling and references, -use hyphen separated, descriptive labels for section references. -Keep in mind that contents may be reorganized later, so -avoid top level names in references like ``user`` or ``devel`` -or ``faq`` unless necessary, because for example the FAQ "what is a -backend?" could later become part of the users guide, so the label: +and then link to with ``:ref:`label-name``` .. code-block:: rst - .. _what-is-a-backend: - -is better than: + See the :ref:`pr-author-guidelines` -.. code-block:: rst +This will render as: - .. _faq-backend: + See the :ref:`pr-author-guidelines` -In addition, since underscores are widely used by Sphinx itself, use -hyphens to separate words. .. _referring-to-other-code: @@ -461,8 +455,8 @@ For clarity, do not use relative links. .. _writing-docstrings: -Write API documentation -======================= +API documentation +================= The API reference documentation describes the library interfaces, e.g. inputs, outputs, and expected behavior. Most of the API documentation is written in docstrings. These are @@ -957,8 +951,8 @@ Example: .. _writing-examples-and-tutorials: -Write examples and tutorials -============================ +Examples and tutorials +====================== Examples and tutorials are Python scripts that are run by `Sphinx Gallery`_. Sphinx Gallery finds ``*.py`` files in source directories and runs the files to @@ -1226,10 +1220,10 @@ Format :code: The code should be about 5-10 lines with minimal customization. Plots in this gallery use the ``_mpl-gallery`` stylesheet for a uniform aesthetic. -Analytics -========== +Website analytics +================= -Documentation page analytics are available at +Analytics of our hosted documentation https://matplotlib.org is available at https://views.scientific-python.org/matplotlib.org. diff --git a/doc/devel/min_dep_policy.rst b/doc/devel/min_dep_policy.rst index 81a84491bc4a..517cc872139e 100644 --- a/doc/devel/min_dep_policy.rst +++ b/doc/devel/min_dep_policy.rst @@ -157,8 +157,8 @@ Matplotlib Python NumPy .. _`1.3`: https://matplotlib.org/1.3.0/users/installing.html#build-requirements -Updating Python and NumPy versions -================================== +Update Python and NumPy versions +================================ To update the minimum versions of Python we need to update: diff --git a/doc/devel/pr_guide.rst b/doc/devel/pr_guide.rst index f29475cbf8d5..8cf31db0254b 100644 --- a/doc/devel/pr_guide.rst +++ b/doc/devel/pr_guide.rst @@ -208,12 +208,21 @@ Review push changes to the contributor branch, or merge the PR and then open a new PR against upstream. -* If you push to a contributor branch leave a comment explaining what +* If you push to a contributor branch, leave a comment explaining what you did, ex "I took the liberty of pushing a small clean-up PR to your branch, thanks for your work.". If you are going to make substantial changes to the code or intent of the PR please check with the contributor first. +* If you find yourself spending too much time on a PR, or feeling frustrated, + it's ok to step back. You can ask for help from other reviewers, or if you are + the only reviewer, you can ask the contributor to find another reviewer or to + wait until you have more time. Make sure to communicate with the contributor + to set the right expectations, e.g. "I currently don't have the bandwidth to + review this PR, but will try to loop someone else in." If you feel like this + PR is not a good fit for the project, you can close it with an explanation or + add the "status: autoclose candidate" label to trigger the autoclose workflow. + .. _pr-approval: Approval diff --git a/doc/devel/release_guide.rst b/doc/devel/release_guide.rst index ccac5b4f8872..eefc31aec07c 100644 --- a/doc/devel/release_guide.rst +++ b/doc/devel/release_guide.rst @@ -45,7 +45,7 @@ versioning scheme: *macro.meso.micro*. .. _release_feature_freeze: -Making the release branch +Create the release branch ========================= .. note:: @@ -379,8 +379,8 @@ to the VER-doc branch and push to GitHub. :: .. _release_bld_bin: -Building binaries -================= +Build binaries +============== We distribute macOS, Windows, and many Linux wheels as well as a source tarball via PyPI. @@ -412,8 +412,8 @@ PyPI. .. _release_upload_bin: -Manually uploading to PyPI -========================== +Manual upload to PyPI +===================== .. note:: diff --git a/doc/devel/style_guide.rst b/doc/devel/style_guide.rst index e35112a65e42..b260872557c5 100644 --- a/doc/devel/style_guide.rst +++ b/doc/devel/style_guide.rst @@ -176,6 +176,51 @@ reliability and consistency in documentation. They are not interchangeable. .. |Axis| replace:: :class:`~matplotlib.axis.Axis` +Headings +-------- +Use sentence case for headings. + +.. table:: + :width: 100% + :widths: 50, 50 + + +------------------------------------+------------------------------------+ + | Correct | Incorrect | + +====================================+====================================+ + | Quick start guide | Quick Start Guide | + +------------------------------------+------------------------------------+ + +Noun phrases and verb phrases are both acceptable for headings. Noun phrases +are preferred for higher-level headings and descriptive sections as they +simply state the content. + +.. table:: + :width: 100% + :widths: 50, 50 + + +------------------------------------+------------------------------------+ + | Correct | Incorrect | + +====================================+====================================+ + | Bug triage and issue curation | Triage bugs and curate issues | + +------------------------------------+------------------------------------+ + +Verb phrases are preferred for instructive and action-oriented sections; in +particular when they cover steps in a process, such as the subsections in +:ref:`installing_for_devs`. + +Use the second-person imperative form of the verb rather than the gerund form. + +.. table:: + :width: 100% + :widths: 50, 50 + + +------------------------------------+------------------------------------+ + | Correct | Incorrect | + +====================================+====================================+ + | Fork the Matplotlib repository | Forking the Matplotlib repository | + +------------------------------------+------------------------------------+ + + Grammar ------- diff --git a/doc/devel/testing.rst b/doc/devel/testing.rst index cbde2bed7979..990b9d0b6493 100644 --- a/doc/devel/testing.rst +++ b/doc/devel/testing.rst @@ -13,10 +13,8 @@ testing infrastructure are in :mod:`matplotlib.testing`. .. _pytest-xdist: https://pypi.org/project/pytest-xdist/ -.. _testing_requirements: - -Requirements ------------- +Prerequisites +------------- To run the tests you will need to :ref:`set up Matplotlib for development `. Note in @@ -34,8 +32,8 @@ particular the :ref:`additional dependencies ` for testing. .. _run_tests: -Running the tests ------------------ +Run the tests +------------- In the root directory of your development repository run:: @@ -82,8 +80,8 @@ to avoid clashes between ``pytest``'s import mode and Python's search path: python -m pytest --import-mode prepend -Viewing image test output -^^^^^^^^^^^^^^^^^^^^^^^^^ +View image test output +^^^^^^^^^^^^^^^^^^^^^^ The output of :ref:`image-based ` tests is stored in a ``result_images`` directory. These images can be compiled into one HTML page, containing @@ -100,34 +98,45 @@ to the folder where the baseline test images are stored. The triage tool require :ref:`QT ` is installed. -Writing a simple test ---------------------- +Write tests +----------- +Tests are located in :file:`lib/matplotlib/tests`. They are organized to mirror +the structure of the code in :file:`lib/matplotlib`. For example, tests for +the ``mathtext.py`` module are in :file:`lib/matplotlib/tests/test_mathtext.py`. + +Naming follows standard pytest conventions: + +- files begin with ``"test_"`` +- test functions begin with ``"test_"`` +- test classes begin with ``"Test"``. + +We prefer simple test functions, but test classes are also acceptable. +Test function names should be descriptive of what they are testing, and long names +like ``test_to_rgba_array_accepts_color_alpha_tuple_with_multiple_colors()`` are +perfectly fine. + +Unit tests +^^^^^^^^^^ -Many elements of Matplotlib can be tested using standard tests. For -example, here is a test from :file:`matplotlib/tests/test_basic.py`:: +Many elements of Matplotlib can be tested using simple unit tests, e.g. :: - def test_simple(): - """ - very simple example test - """ - assert 1 + 1 == 2 + def test_to_rgba_explicit_alpha_overrides_tuple_alpha(): + assert mcolors.to_rgba(('red', 0.1), alpha=0.9) == (1, 0, 0, 0.9) -Pytest determines which functions are tests by searching for files whose names -begin with ``"test_"`` and then within those files for functions beginning with -``"test"`` or classes beginning with ``"Test"``. +Data in tests +^^^^^^^^^^^^^ +Try to use minimal explicit data, such as +``[1, 2, 3]``, ``range(5)`` or ``np.arange(5)``, because it +makes the test more readable. -Some tests have internal side effects that need to be cleaned up after their -execution (such as created figures or modified `.rcParams`). The pytest fixture -``matplotlib.testing.conftest.mpl_test_settings`` will automatically clean -these up; there is no need to do anything further. +When you need more and non-trivial data, generate it programmatically, e.g. :: -Random data in tests --------------------- + x = np.linspace(0, 2*np.pi, 101) + y = 2 * np.sin(x) + 1 -Random data is a very convenient way to generate data for examples, -however the randomness is problematic for testing (as the tests -must be deterministic!). To work around this set the seed in each test. -For numpy's default random number generator use:: +Use random numbers only when an algorithmic way to generate the data is too +cumbersome or impossible. In this case, set the seed to a fixed value to make +the test deterministic. For numpy's default random number generator use :: import numpy as np rng = np.random.default_rng(19680801) @@ -136,10 +145,56 @@ and then use ``rng`` when generating the random numbers. The seed is :ref:`John Hunter's ` birthday. +Test cleanup +^^^^^^^^^^^^ +We often need to create figures or to modify `.rcParams` to test some functionality. +Cleanup of such side effects is handled automatically through a pytest fixture +(``matplotlib.testing.conftest.mpl_test_settings``) so that no manual cleanup is +necessary. + +In particular, you don't need to call ``plt.close()``. + +Testing with figures and Axes +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +When you need figures and/or Axes, create them through the standard methods +(``plt.figure()``, ``plt.subplots()``, etc.). + +Creating figures and Axes is rather expensive (>100ms). Only create as many as you need for +the test, and reuse them if possible. It is perfectly fine to test multiple parametrizations +or related functionality in one test; i.e. extend the classical test structure +*Arrange–Act–Assert* with multiple *Act-Assert* blocks, e.g. :: + + def test_stackplot_facecolor(): + # Test that facecolors are properly passed and take precedence over colors parameter + x = np.linspace(0, 10, 10) + y1 = 1.0 * x + y2 = 2.0 * x + 1 + + fig, ax = plt.subplots() + + facecolors = ['r', 'b'] + + colls = ax.stackplot(x, y1, y2, facecolor=facecolors, colors=['c', 'm']) + for coll, fcolor in zip(colls, facecolors): + assert mcolors.same_color(coll.get_facecolor(), fcolor) + + # Plural alias should also work + colls = ax.stackplot(x, y1, y2, facecolors=facecolors, colors=['c', 'm']) + for coll, fcolor in zip(colls, facecolors): + assert mcolors.same_color(coll.get_facecolor(), fcolor) + +Assert values rather than visual results when feasible. This is clearer, +less computationally expensive and less fragile than comparing images, e.g. :: + + def test_savefig_preserve_layout_engine(): + fig = plt.figure(layout='compressed') + fig.savefig(io.BytesIO(), bbox_inches='tight') + assert fig.get_layout_engine()._compress + .. _image-comparison: -Writing an image comparison test --------------------------------- +Testing with reference images +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Writing an image-based test is only slightly more difficult than a simple test. The main consideration is that you must specify the "baseline", or @@ -180,9 +235,8 @@ texts (labels, tick labels, etc) are not really part of what is tested, use the will lead to smaller figures and reduce possible issues with font mismatch on different platforms. - -Compare two methods of creating an image -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Testing by comparing two methods to create an image +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Baseline images take a lot of space in the Matplotlib repository. An alternative approach for image comparison tests is to use the @@ -228,15 +282,8 @@ See the documentation of `~matplotlib.testing.decorators.image_comparison` and `~matplotlib.testing.decorators.check_figures_equal` for additional information about their use. -Creating a new module in matplotlib.tests ------------------------------------------ - -We try to keep the tests categorized by the primary module they are -testing. For example, the tests related to the ``mathtext.py`` module -are in ``test_mathtext.py``. - -Using GitHub Actions for CI ---------------------------- +CI with GitHub Actions +---------------------- `GitHub Actions `_ is a hosted CI system "in the cloud". @@ -262,8 +309,8 @@ https://github.com/your_GitHub_user_name/matplotlib/actions -- here's `an example `_. -Using tox ---------- +tox: Test multiple python versions +---------------------------------- `Tox `_ is a tool for running tests against multiple Python environments, including multiple versions of Python @@ -303,8 +350,8 @@ tests are run. For more info on the ``tox.ini`` file, see the `Tox Configuration Specification `_. -Building old versions of Matplotlib ------------------------------------ +Build old versions of Matplotlib +-------------------------------- When running a ``git bisect`` to see which commit introduced a certain bug, you may (rarely) need to build very old versions of Matplotlib. The following @@ -312,8 +359,8 @@ constraints need to be taken into account: - Matplotlib 1.3 (or earlier) requires numpy 1.8 (or earlier). -Testing released versions of Matplotlib ---------------------------------------- +Test released versions of Matplotlib +------------------------------------ Running the tests on an installation of a released version (e.g. PyPI package or conda package) also requires additional setup. diff --git a/doc/devel/triage.rst b/doc/devel/triage.rst index ca06fd515c79..f151fa1faf64 100644 --- a/doc/devel/triage.rst +++ b/doc/devel/triage.rst @@ -1,9 +1,9 @@ .. _bug_triaging: -******************************* -Bug triaging and issue curation -******************************* +***************************** +Bug triage and issue curation +***************************** The `issue tracker `_ is important to communication in the project because it serves as the @@ -30,35 +30,29 @@ are not part of the Matplotlib organization do not have `permissions to change milestones, add labels, or close issue `_. -If you do not have enough GitHub permissions do something (e.g. add a -label, close an issue), please leave a comment with your -recommendations! +If you do not have enough GitHub permissions to do something (e.g. add a +label, close an issue), please leave a comment with your recommendations! The following actions are typically useful: -- documenting issues that are missing elements to reproduce the problem - such as code samples - -- suggesting better use of code formatting (e.g. triple back ticks in the - markdown). - -- suggesting to reformulate the title and description to make them more - explicit about the problem to be solved - -- linking to related issues or discussions while briefly describing +* documenting issues that are missing elements to reproduce the problem, + such as code samples; +* suggesting better use of code formatting (e.g. triple back ticks in the + markdown); +* suggesting to reformulate the title and description to make them more + explicit about the problem to be solved; +* linking to related issues or discussions while briefly describing how they are related, for instance "See also #xyz for a similar attempt at this" or "See also #xyz where the same thing was - reported" provides context and helps the discussion - -- verifying that the issue is reproducible - -- classify the issue as a feature request, a long standing bug or a - regression + reported", which provides context and helps the discussion; +* verifying that the issue is reproducible; +* classifying the issue as a feature request, a long standing bug or a + regression. .. topic:: Fruitful discussions - Online discussions may be harder than it seems at first glance, in - particular given that a person new to open-source may have a very + Online discussions may be harder than they seem at first glance, in + particular given that a person new to open source may have a very different understanding of the process than a seasoned maintainer. Overall, it is useful to stay positive and assume good will. `The @@ -73,31 +67,26 @@ Maintainers and triage team members In addition to the above, maintainers and the triage team can do the following important tasks: -- Update labels for issues and PRs: see the list of `available GitHub +* Update labels for issues and PRs: see the list of `available GitHub labels `_. +* Triage issues: -- Triage issues: - - - **reproduce the issue**, if the posted code is a bug label the issue - with "status: confirmed bug". - - - **identify regressions**, determine if the reported bug used to + * **reproduce the issue**, and if the posted code is a bug label the issue + with `status: confirmed bug `_. + * **identify regressions**, determine if the reported bug used to work as expected in a recent version of Matplotlib and if so determine the last working version. Regressions should be milestoned for the next bug-fix release and may be labeled as "Release critical". - - - **close usage questions** and politely point the reporter to use - `discourse `_ or Stack Overflow - instead and label as "community support". - - - **close duplicate issues**, after checking that they are + * **close duplicate issues**, after checking that they are indeed duplicate. Ideally, the original submitter moves the - discussion to the older, duplicate issue - - - **close issues that cannot be replicated**, after leaving time (at - least a week) to add extra information - + discussion to the older, duplicate issue. + * **close issues that cannot be replicated**, after leaving time (at + least a week) to add extra information. + * **invite contributors to engage with the community** if the issue requires + more information or discussion. These discussions can take place in the + `weekly community meetings `__, or + on `discourse `__. .. topic:: Closing issues: a tough call @@ -107,13 +96,6 @@ important tasks: question or has been considered as unclear for many years, then it should be closed. -Preparing PRs for review -======================== - -Reviewing code is also encouraged. Contributors and users are welcome to -participate to the review process following our :ref:`review guidelines -`. - .. _triage_workflow: Triage workflow @@ -127,13 +109,19 @@ The following workflow is a good way to approach issue triaging: Matplotlib project itself, beyond just using the library. As such, we want it to be a welcoming, pleasant experience. -#. Is this a usage question? If so close it with a polite message. +#. Is this a usage question? + + If so, close it with a polite message, point the reporter to use + `discourse `__ or Stack Overflow instead + and use the + `community support `__ + label, if you have the necessary permissions. #. Is the necessary information provided? Check that the poster has filled in the issue template. If crucial information (the version of Python, the version of Matplotlib used, - the OS, and the backend), is missing politely ask the original + the OS, and the backend) is missing, politely ask the original poster to provide the information. #. Is the issue minimal and reproducible? @@ -154,7 +142,7 @@ The following workflow is a good way to approach issue triaging: OS, Python, and Matplotlib versions. If we need more information from either this or the previous step - please label the issue with "status: needs clarification". + please label the issue with `status: needs clarification `_. #. Is this a regression? @@ -169,7 +157,6 @@ The following workflow is a good way to approach issue triaging: `_ to find the first commit where it was broken. - #. Is this a duplicate issue? We have many open issues. If a new issue seems to be a duplicate, @@ -182,32 +169,69 @@ The following workflow is a good way to approach issue triaging: slightly different example, add it to the original issue as a comment or an edit to the original post. - Label the closed issue with "status: duplicate" + Label the closed issue with `status: duplicate `__. #. Make sure that the title accurately reflects the issue. If you have the necessary permissions edit it yourself if it's not clear. -#. Add the relevant labels, such as "Documentation" when the issue is - about documentation, "Bug" if it is clearly a bug, "New feature" if it - is a new feature request, ... +#. Add the relevant labels, such as `Documentation `__ + when the issue is about documentation, `status: confirmed bug `__ + if it is clearly a bug, `New feature `__ + if it is a new feature request, etc. + + An additional useful step can be to tag with the relevant "topic: ..." label, + e.g. "topic: widgets/UI" or "topic: animation". + + Take some time to familiarize yourself with the available labels and their + meaning, and try to use them consistently. + +.. topic:: Good first issues + + If the issue is clearly defined, the fix seems relatively straightforward, + and there is consensus on what the solution is among maintainers, label the + issue as + `Good first issue `_ + (and possibly a description of the fix or a hint as to where in the + code base to look to get started). - If the issue is clearly defined and the fix seems relatively - straightforward, label the issue as “Good first issue” (and - possibly a description of the fix or a hint as to where in the - code base to look to get started). + Note that good first issues are intended to onboard newcomers with a genuine + interest in improving Matplotlib, in the hopes that they will continue to + participate in our development community; therefore, the use of AI tools to + resolve these issues is not appropriate. - An additional useful step can be to tag the corresponding module e.g. - the "GUI/Qt" label when relevant. +Preparing PRs for review +======================== + +Doing initial reviews of contributions is also encouraged. Contributors and +users are welcome to participate to the review process following our +:ref:`review guidelines `. In particular, if you identify a PR +that needs maintainer attention, you can add the +`status: needs review `_ +label to it, or add it to the next community meeting agenda for discussion. You +can: + +* Suggest fixes to CI check failures, such as failing tests or documentation + builds; +* Help with :ref:`rebasing instructions `; +* Suggest improvements to the PR description, including filling out the AI + Disclosure section if it is missing. + +AI-generated contributions +-------------------------- + +Make sure PRs comply with our :ref:`AI policy `. If you identify +a PR that does not comply with the policy, ask the contributor to clarify the AI +tools used and the contribution of the author, and to update the PR description +accordingly to comply with our AI policy. .. _triage_team: Triage team =========== - If you would like to join the triage team: -1. Correctly triage 2-3 issues. +1. Correctly triage 2-3 issues or review 2-3 pull requests, as described above. 2. Ask someone on in the Matplotlib organization (publicly or privately) to recommend you to the triage team (look for "Member" on the top-right of comments on GitHub). If you worked with someone on the issues triaged, they @@ -215,4 +239,15 @@ If you would like to join the triage team: 3. Responsibly exercise your new power! Anyone with commit or triage rights may nominate a user to be invited to join -the triage team by emailing matplotlib-steering-council@numfocus.org . +the triage team by nominating them through the private "Triage team nominations" +category on `Discourse `__ (Note that only +``@maintainers`` and ``@triage`` members can see this category). The nomination +will then be confirmed by the Steering Council and the user, if accepted, will +be added to the triage team on GitHub. + +If no objections are raised within one week of the nomination, a member with the ``owner`` role on GitHub will: +1. Send an invitation email to the nominee following a template. +2. Once the nominee responds affirmatively, they will add the nominee to the Triage group on GitHub, and to the ``@triage`` group on Discourse. +3. Close the Discourse thread with a confirmation that the nomination was accepted (or turned down). + +If objections are raised, no action will be taken and the nomination can be revisited in the future. diff --git a/doc/install/dependencies.rst b/doc/install/dependencies.rst index 8f638ea5ed1d..578fab93ed5a 100644 --- a/doc/install/dependencies.rst +++ b/doc/install/dependencies.rst @@ -232,7 +232,7 @@ Python ``pip`` normally builds packages using :external+pip:doc:`build isolation `, which means that ``pip`` installs the dependencies listed here for the -duration of the build process. However, build isolation is disabled via the the +duration of the build process. However, build isolation is disabled via the :external+pip:ref:`--no-build-isolation ` flag when :ref:`installing Matplotlib for development `, which means that the dependencies must be explicitly installed, either by :ref:`creating a virtual environment ` diff --git a/doc/release/github_stats.rst b/doc/release/github_stats.rst index e01a1727b162..d6beb7b6b059 100644 --- a/doc/release/github_stats.rst +++ b/doc/release/github_stats.rst @@ -2,107 +2,1361 @@ .. _github-stats: -GitHub statistics for 3.10.9 (Apr 23, 2026) +GitHub statistics for 3.11.0 (May 12, 2026) =========================================== -GitHub statistics for 2024/12/14 (tag: v3.10.0) - 2026/04/23 +GitHub statistics for 2024/12/14 (tag: v3.10.0) - 2026/05/12 These lists are automatically generated, and may be incomplete or contain duplicates. -We closed 10 issues and merged 34 pull requests. -The full list can be seen `on GitHub `__ +We closed 257 issues and merged 812 pull requests. +The full list can be seen `on GitHub `__ -The following 37 authors contributed 519 commits. +The following 266 authors contributed 4674 commits. +* 34j +* Aaratrika-Shelly +* Aaron Meurer * Aasma Gupta +* Abhiroop Batabyal +* Abitamim Bharmal +* Adam Ormondroyd +* AdamOrmondroyd +* Aditya Singh +* aditya-singh597 +* AdrashDec +* Aishling Cooke +* Alan Burlot +* Albert Y. Shih +* ALBIN BABU VARGHESE +* albus-droid +* Alexandra Khoo +* Allison +* alphanoobie +* AMAN KUSHWAHA +* Aman Kushwaha +* Aman Nijjar +* Aman Parganiha * Aman Srivastava +* Amisha Mehta +* amishamehta99 +* Amitesh Singh +* Anabelle VanDenburgh +* Andrea Alberti +* Andres Gutierrrez +* Andrew Landau +* Andrés Gutierrez +* Anselm Hahn +* anTon +* Anton * Antony Lee +* Archil Jain +* Arnaud Patard +* Barbier--Darnal Joseph * beelauuu +* Ben Greiner * Ben Root +* Bodhi Silberling +* Brian Christian +* Brian Lau +* BriAnna Foreman +* brk +* Carlos Ramos Carreño +* Cemonix +* Chaoyi Hu +* Charlie Thornton +* Chirag Sharma +* Chirag3841 +* chrisjbillington * Christine P. Chai +* clairefio +* Clemens Brunner +* Clément Robert +* cmp0xff +* Colton Lathrop +* Constantinos Menelaou +* Corenthin ZOZOR +* cvanelteren +* Daniel Weiss +* Danny +* David Lowry-Duda * David Stansby * dependabot[bot] +* DerWeh +* Diksha +* Dominik Stiller +* Doron Behar +* Duncan Macleod +* DWesl +* Edge-Seven +* ee25b003 +* ellie * Elliott Sales de Andrade -* G.D. McBain +* Emmanuel Ferdman +* EncryptedDoom +* Eric Firing +* Eric Larson +* Evgenii Radchenko +* Eytan Adler +* Fazeel Usmani +* founta +* francisayyad03 +* Francisco Cardozo +* G Karthik Koundinya +* G\. D\. McBain +* G26Karthik +* ganglike +* Geoffrey Thomas +* Gguidini * Greg Lucas +* guillermodotn * hannah +* Hannan7812 +* Hasan Rashid +* Hassan Kibirige +* heinrich5991 * hu-xiaonan +* Husain Gadiwala +* Ian Hunt-Isaak * Ian Thomas +* ianlv +* IdiotCoffee +* ilakk manoharan +* Ilakkuvaselvi Manoharan +* intelliking * Inês Cachola +* ishan372or +* James Addison +* Javier Pérez Robles +* jaya prajapati +* jayaprajapatii +* Jaylon +* Jimmy Shah +* jocelynvj +* JOD +* joddeepesh-cloud * Jody Klymak +* Johannes Kopton +* Jonas Drotleff +* Jonathan Reimer * Jouni K. Seppänen +* Julian Chen +* Kaustbh +* Kaustubh +* kdpenner * Khushi_29 +* Khushikela29 +* KIU Shueng Chuan +* konmenel +* Kris Rubiano +* kusch lionel +* Kyle Martin * Kyle Sunden +* Kyra Cho +* landoskape +* LangQi99 +* Larry Bradley +* leakyH +* Leo Singer +* Leon Merten Lohse +* lilfer +* litchi +* Logan Pageler +* Logan-Pageler +* Lucas Gruwez +* Lucx33 +* Luka Aladashvili +* Lukas Hergt +* lukashergt * Lumberbot (aka Jack) +* Lívia Lutz * m-sahare +* Mafalda Botelho +* Manit Roy +* manit2004 +* Manthan Nagvekar +* marbled-toast +* Marco Barbosa +* Marco Gorelli +* Marie +* Marten H. van Kerkwijk +* Marten Henric van Kerkwijk +* martincornejo +* masih.khatibzdeh +* Mateusz Sokół +* Matthew Feickert +* Melissa Weber Mendonça +* Melwyn Francis Carlo +* MengAiDev +* Milan Gittler +* MiniX16 +* Miriam +* Miriam Simone +* miriamsimone +* MKhatibzadeh +* Mohit Pal +* Moniza Kidwai +* MQY +* mromanie +* Muhammad Hannan Akram +* musvaage * N R Navaneet +* NabeelShar +* nakano * Nathan G. Wiseman +* Nathan Goldbaum +* Nathan Hansen +* Nathan McDougall +* Nick Coish +* Nicolai Weitkemper +* Niklas Mertsch +* null-dreams +* Obliman * Oscar Gustafsson +* Owl +* Parsa Homayouni +* Patrick Seitz +* Pedro Marques +* pedrom2002 +* Pieter Eendebak +* Pirzada Ahmad Faraz +* pirzada-ahmadfaraz * Praful Gulani +* Pranav +* Pranav Raghu +* pre-commit-ci[bot] +* proximalf +* q33566 * Qian Zhang +* r3kste +* Rafael Katri +* Rahul +* Rahul Monani * Raphael Erik Hviding * Raphael Quast +* RETHICK CB +* Ricardo Peres +* RogueRebel33 * Roman +* Roman A * Ruth Comer +* ruvilonix +* Ryan May +* Saakshi Gupta +* Sai Chaitanya, Sanivada * saikarna913 +* Sanchit Rishi +* Saumya * Scott Shambaugh +* Sebastien Wieckowski +* Siddharth_Savani +* Sonu Singh +* star1327p +* statxc +* Stefan van der Walt +* Stefan Vujadinovic * Steve Berardi +* Steve Nicholson +* tfpf * Thomas A Caswell +* thomashopkins32 +* Tiago Marques +* Tim Heap * Tim Hoffmann +* Timon Erhart +* Tine Zivic +* Tingwei Zhu * Trygve Magnus Ræder +* Ubuntu +* Vagner Messias +* Vedant Madane +* Victor Liu +* Vidya * Vikash Kumar +* Vishal Pankaj Chandratreya +* Vraj Rajpura +* Weh Andreas +* Wiliam +* Yuepeng Gu +* Zhongqi LUO +* ZPyrolink GitHub issues and pull requests: -Pull Requests (34): - -* :ghpull:`31556`: FIX: Inverted PyErr_Occurred check in enum type caster (_enums.h) -* :ghpull:`31078`: Backport PR #31075 on branch v3.10.x (Fix remove method for figure title and xy-labels) -* :ghpull:`31280`: Backport PR #31278 on branch v3.10.x (Fix ``clabel`` manual argument not accepting unit-typed coordinates) -* :ghpull:`31520`: Backport PR #31020 on branch v3.10.x (DOC: Fix doc builds with Sphinx 9) -* :ghpull:`31511`: Backport PR #31504 on branch v3.10.x (Re-order variants to prioritize narrower types) -* :ghpull:`31504`: Re-order variants to prioritize narrower types -* :ghpull:`31445`: Backport PR #31437: mathtext: Fix type inconsistency with fontmaps -* :ghpull:`31437`: mathtext: Fix type inconsistency with fontmaps -* :ghpull:`31411`: Backport PR #31323 on branch v3.10.x (FIX: Prevent crash when removing a subfigure containing subplots) -* :ghpull:`31421`: Backport PR #31420 on branch v3.10.x (Fix outdated Savannah URL for freetype download) -* :ghpull:`31420`: Fix outdated Savannah URL for freetype download -* :ghpull:`31418`: Backport PR #31401: BLD: Temporarily pin setuptools-scm<10 -* :ghpull:`31323`: FIX: Prevent crash when removing a subfigure containing subplots -* :ghpull:`31401`: BLD: Temporarily pin setuptools-scm<10 -* :ghpull:`31278`: Fix ``clabel`` manual argument not accepting unit-typed coordinates -* :ghpull:`31154`: Backport PR #31153 on branch v3.10.x (TST: Use correct method of clearing mock objects) -* :ghpull:`31153`: TST: Use correct method of clearing mock objects -* :ghpull:`31075`: Fix remove method for figure title and xy-labels -* :ghpull:`31036`: Backport PR #31035 on branch v3.10.x (DOCS: Fix typo in time array step size comment) -* :ghpull:`30986`: Backport PR #30985 on branch v3.10.x (MNT: do not assign a numpy array shape) -* :ghpull:`30985`: MNT: do not assign a numpy array shape -* :ghpull:`30971`: Backport PR #30969 on branch v3.10.x (DOC: Simplify barh() example) -* :ghpull:`30965`: Backport PR #30952 on branch v3.10.x (DOC: Tutorial on API shortcuts) -* :ghpull:`30964`: Backport PR #30960 on branch v3.10.x (SVG backend - handle font weight as integer) -* :ghpull:`30960`: SVG backend - handle font weight as integer -* :ghpull:`30924`: Backport PR #30910 on branch v3.10.x (DOC: Improve writer parameter docs of Animation.save()) -* :ghpull:`30870`: Backport PR #30869 on branch v3.10.x (FIX: Accept array for zdir) -* :ghpull:`30869`: FIX: Accept array for zdir -* :ghpull:`30860`: Backport PR #30858 on branch v3.10.x (DOC: reinstate "codex" search term) -* :ghpull:`30818`: Backport PR #30817 on branch v3.10.x (Update sphinx-gallery header patch) -* :ghpull:`30801`: Backport PR #30763 on branch v3.10.x (DOC: Add example how to align tick labels) -* :ghpull:`30791`: Backport PR #30788 on branch v3.10.8-doc (Fix typo in key-mapping for "f11") -* :ghpull:`30790`: Backport PR #30788 on branch v3.10.x (Fix typo in key-mapping for "f11") -* :ghpull:`30788`: Fix typo in key-mapping for "f11" - -Issues (10): - -* :ghissue:`31495`: Unavoidable warnings with pybind11 main branch -* :ghissue:`31433`: [MNT]: Mypy error -* :ghissue:`31340`: [Bug]: outdated savannah URL in subprojects/freetype-2.6.1.wrap -* :ghissue:`31319`: [Bug]: Crash when removing a subfigure with a subplot in a figure -* :ghissue:`27525`: [Bug]: clabel manual argument does not accept units -* :ghissue:`31112`: [TST] Upcoming dependency test failures -* :ghissue:`31073`: [Bug]: Crash when Removing Suptitle in a Figure with Constrained Layout -* :ghissue:`30981`: [TST] Upcoming dependency test failures -* :ghissue:`30868`: [Bug]: Axe3D text() method does not allow zdir=numpy.array(...) -* :ghissue:`21566`: [ENH]: set_horizontalalignment("right") on Y axis labels when yaxis.ticks_right() is used. +Pull Requests (812): + +* :ghpull:`31662`: Backport PR #31659 on branch v3.11.x (ci: Re-arrange AppVeyor pipeline) +* :ghpull:`31659`: ci: Re-arrange AppVeyor pipeline +* :ghpull:`31658`: Backport PR #31578 on branch v3.11.x (FIX: URL links in SVG should have target='_blank') +* :ghpull:`31578`: FIX: URL links in SVG should have target='_blank' +* :ghpull:`31654`: Backport PR #30108 on branch v3.11.x (Fix constrained layout applying pad multiple times) +* :ghpull:`30108`: Fix constrained layout applying pad multiple times +* :ghpull:`31651`: Backport PR #31649 on branch v3.11.x (DOC: Prevent ticks from being cut off in tick rotation example) +* :ghpull:`31650`: Backport PR #31647 on branch v3.11.x (FIX: Pin rstcheck to prevent CI failure) +* :ghpull:`31649`: DOC: Prevent ticks from being cut off in tick rotation example +* :ghpull:`31647`: FIX: Pin rstcheck to prevent CI failure +* :ghpull:`31646`: Backport PR #31632 on branch v3.11.x (FIX: Prohibit special TeX chars in pgf metadata) +* :ghpull:`31632`: FIX: Prohibit special TeX chars in pgf metadata +* :ghpull:`31643`: Backport PR #31609 on branch v3.11.x (DOC: Improve autoscaling and margin docs) +* :ghpull:`31644`: Backport PR #31579 on branch v3.11.x (DOC: Document that bar() errorbars do not support individual coloring) +* :ghpull:`31579`: DOC: Document that bar() errorbars do not support individual coloring +* :ghpull:`31609`: DOC: Improve autoscaling and margin docs +* :ghpull:`31640`: Backport PR #31638 on branch v3.11.x (Bump the actions group with 2 updates) +* :ghpull:`31639`: Backport PR #31628 on branch v3.11.x (FIX: use axis lines tight bbox within axis artist tight bbox) +* :ghpull:`31638`: Bump the actions group with 2 updates +* :ghpull:`31637`: Backport PR #31634 on branch v3.11.x (Fix some font-related issues) +* :ghpull:`31628`: FIX: use axis lines tight bbox within axis artist tight bbox +* :ghpull:`31634`: Fix some font-related issues +* :ghpull:`31636`: Backport PR #31630 on branch v3.11.x (Restore PolarTransform(apply_theta_transforms) parameter) +* :ghpull:`31630`: Restore PolarTransform(apply_theta_transforms) parameter +* :ghpull:`31631`: Backport PR #31557 on branch v3.11.x (FIX: Added ft2font null checks added) +* :ghpull:`31629`: Backport PR #31621 on branch v3.11.x (Make Scale axis parameter handling more flexible) +* :ghpull:`31557`: FIX: Added ft2font null checks added +* :ghpull:`31621`: Make Scale axis parameter handling more flexible +* :ghpull:`31627`: Backport PR #31625 on branch v3.11.x (DOC: Inline ScalarMappable reStructuredText entries) +* :ghpull:`31626`: Backport PR #25478 on branch v3.11.x ([BUG] Fix alpha bug on 3D PathCollection plots.) +* :ghpull:`31625`: DOC: Inline ScalarMappable reStructuredText entries +* :ghpull:`25478`: [BUG] Fix alpha bug on 3D PathCollection plots. +* :ghpull:`31611`: Backport PR #31608 on branch v3.11.x (Remove outdated comment re: implementation of hinting_factor.) +* :ghpull:`31608`: Remove outdated comment re: implementation of hinting_factor. +* :ghpull:`31602`: Backport PR #31599 on branch v3.11.x (Bump the actions group with 2 updates) +* :ghpull:`31603`: Backport PR #31594 on branch v3.11.x (DOC: Explain how to selectively restore ticks that are removed by sharex) +* :ghpull:`31594`: DOC: Explain how to selectively restore ticks that are removed by sharex +* :ghpull:`31601`: Backport PR #31600 on branch v3.11.x (Bump https://github.com/astral-sh/ruff-pre-commit from v0.15.11 to 0.15.12) +* :ghpull:`31599`: Bump the actions group with 2 updates +* :ghpull:`31600`: Bump https://github.com/astral-sh/ruff-pre-commit from v0.15.11 to 0.15.12 +* :ghpull:`31592`: Backport PR #31588 on branch v3.11.x (Expire some missed deprecations from 3.9) +* :ghpull:`31588`: Expire some missed deprecations from 3.9 +* :ghpull:`31583`: Backport PR #31577 on branch v3.11.x (FIX: Polar Radial Tick Warnings Labels Bug) +* :ghpull:`31577`: FIX: Polar Radial Tick Warnings Labels Bug +* :ghpull:`31582`: Backport PR #31580 on branch v3.11.x (DOC: added unregister to colormap guide) +* :ghpull:`31580`: DOC: added unregister to colormap guide +* :ghpull:`31564`: Backport PR #31563 on branch v3.11.x (LIC: remove carlogo license) +* :ghpull:`31563`: LIC: remove carlogo license +* :ghpull:`31561`: Fixed bug with an uninitialized colormap in parallel threads +* :ghpull:`31555`: FIX: removing colorbar's axes also removes colorbar +* :ghpull:`31560`: merge up v3.10.9 +* :ghpull:`31416`: MNT: Privatize Formatter attributes +* :ghpull:`23616`: feat(mathtext): support underline +* :ghpull:`31554`: BUG: avoid a deprecation warning from numpy 2.5 (calling ``datetime64('NaT')`` without a unit is deprecated) +* :ghpull:`31535`: DOC: fix broken link to wxPython Widget Inspection Tool +* :ghpull:`31551`: Bump https://github.com/pre-commit/mirrors-mypy from v1.20.1 to 1.20.2 +* :ghpull:`31552`: Bump scientific-python/upload-nightly-action from 0.6.3 to 0.6.4 in the actions group +* :ghpull:`31478`: Fix errorbar autoscaling inconsistency on log axes +* :ghpull:`31522`: MNT: Update all pre-commit hooks +* :ghpull:`31365`: Add thumbnail for embedding in user interfaces examples +* :ghpull:`31530`: BUG: Fix relim() to support Collection artists (scatter, etc.) +* :ghpull:`31514`: Add suggestions to more lookup errors +* :ghpull:`31465`: lib/matplotlib/tests/test_inset.py: Fix tolerance on aarch64 +* :ghpull:`31521`: Drop support for font hinting factor +* :ghpull:`31492`: MNT: Ensure all types from matplotlib.typing are documented +* :ghpull:`31524`: FIX: Disallow twinx/twiny on Axes3D +* :ghpull:`31540`: DOC: replace dolphin license RDF block with prose attribution +* :ghpull:`31426`: Fix: Optimize Cursor clearing on mouse exit to prevent lag +* :ghpull:`31512`: Document that ``TimedAnimation`` should not be used +* :ghpull:`31518`: DOC: add tags to tick locator and formatter examples +* :ghpull:`31519`: Bump the actions group with 3 updates +* :ghpull:`31517`: [DOC] make headers in pie example consistent +* :ghpull:`31515`: Remove unnecessary ruff lint exceptions +* :ghpull:`31516`: TST: account for flakiness with Numpy v1 (part 3) +* :ghpull:`31489`: Fixed: specified exception type in cbook.py +* :ghpull:`31314`: DOC: setting active axes position is ineffective +* :ghpull:`31148`: TST: Use explicit style in all image_comparison calls +* :ghpull:`31486`: ENH: Add an environment variable to ignore system fonts +* :ghpull:`31507`: PR template: always ask for AI declaration +* :ghpull:`31503`: TST: Harden handling of Popen subprocesses +* :ghpull:`31490`: DOC: Minor style improvement of radio buttons examples +* :ghpull:`31181`: ENH: Give control whether twinx() or twiny() overlays the main axis +* :ghpull:`31485`: MNT: Update bundled font libraries +* :ghpull:`31484`: MNT: Use new defaults in set_font_settings_for_testing +* :ghpull:`31483`: Bump the actions group across 1 directory with 2 updates +* :ghpull:`31476`: DOC: Improve Radio Buttons example +* :ghpull:`31275`: DOC: use minigallery for tutorial thumbnails +* :ghpull:`29763`: Shorten Agg template usage with class template argument deduction. +* :ghpull:`31353`: Fix #21409: Make twin axes inherit parent position +* :ghpull:`31431`: FIX: Guard against already-removed labels in ContourSet.remove() +* :ghpull:`31428`: Relax type hints for xy and xytext in annotate +* :ghpull:`31468`: DOC: Replace ``skip_deprecated`` extension by standard Sphinx metadata +* :ghpull:`30161`: Font and text overhaul +* :ghpull:`31461`: Support font features/language in default RendererBase.draw_text +* :ghpull:`31303`: TST: Reset tolerances on tests changed by text overhaul +* :ghpull:`31471`: DOC: Use FuncAnimation in 3D animations +* :ghpull:`31477`: DOC: Improve Radio Buttons Grid example +* :ghpull:`31470`: MNT: Deprecate matplotlib.image.thumbnail +* :ghpull:`31475`: Purge gitter links +* :ghpull:`31466`: DOC: make simple animation example easier to find +* :ghpull:`31469`: Change if condition to allow handles to be passed as a ndarray and not only Python list or tuple, etc. +* :ghpull:`31459`: DOC: Improve AI policy +* :ghpull:`31444`: Bump the actions group with 3 updates +* :ghpull:`31456`: Clarify fonttype switch in backend_pdf. +* :ghpull:`31300`: TST: Set tests touched by text overhaul to mpl20 style +* :ghpull:`31449`: Fix: improve log-scale error message wording +* :ghpull:`30385`: Add type stubs for functions in matplotlib.dates +* :ghpull:`31442`: TST: account for flakiness with Numpy v1 (part 2) +* :ghpull:`31440`: Fix FreeType runtime version check +* :ghpull:`31295`: TST: Cleanup back-compat code in tests touched by text overhaul +* :ghpull:`31408`: Merge branch 'main' into text-overhaul +* :ghpull:`31407`: BLD: Update bundled FreeType to 2.14.3 +* :ghpull:`31439`: Clarify SecondaryAxes limit behavior via documentation +* :ghpull:`31432`: DOC: More concise page title: Development setup +* :ghpull:`31423`: DOC: Remove pyplot vs. OO interface discussion from lifecycle example +* :ghpull:`31413`: ENH: Support partial figsize with None (#31400) +* :ghpull:`31368`: Fix: Prevent Cursor blitting from erasing overlapping axes (#25670) +* :ghpull:`31409`: Bump the actions group with 2 updates +* :ghpull:`31417`: DOC: Explain return value of secondary_x/yaxis +* :ghpull:`31412`: MNT: Minor cleanup of label formatting in PathCollection.legend_elements +* :ghpull:`31422`: Improve legend loc and bbox_to_anchor documentation (#26620) +* :ghpull:`31414`: DOC: Improve Formatter documentation +* :ghpull:`31419`: Add a short example to StrMethodFormatter docstring +* :ghpull:`31405`: Tweak secondary_{x,y}axis docs. +* :ghpull:`31372`: BLD: Update bundled libraqm to 0.10.4 +* :ghpull:`31198`: Allow tuning the shape of {L,R,D}Arrow tips. +* :ghpull:`31183`: ENH: Allow fonts to be addressed by any of their SFNT family names +* :ghpull:`31371`: ps/pdf: Override font height metrics to support AFM files +* :ghpull:`31343`: TST: Restore some tolerances for some arch/platform-specific failures +* :ghpull:`31248`: SEC: Remove eval() from validate_cycler +* :ghpull:`31395`: doc: mention ``bar_label`` in ``bar`` and ``barh`` +* :ghpull:`31385`: Make font search case insensitive in logo example +* :ghpull:`31399`: DOC: Rename gallery README.txt files to GALLERY_HEADER.rst +* :ghpull:`29998`: Implement head resizing (and reversal) for larrow/rarrow/darrow +* :ghpull:`24744`: Addresses issue #24618 "Road sign" boxstyle/annotation, alternative to #24697 +* :ghpull:`31392`: Tweak Formatter method docstrings. +* :ghpull:`31200`: DOC: moderation and enforcement +* :ghpull:`30513`: TST: Remove redundant font tests +* :ghpull:`31363`: Update black requirement from <26 to <27 +* :ghpull:`31355`: Bump the actions group across 1 directory with 8 updates +* :ghpull:`31370`: Update dead link for Ware 1988 in colormap docs +* :ghpull:`31357`: ci: Configure dependabot to skip minver requirements +* :ghpull:`31358`: TST: Replace pywin32 with ctypes wrapper +* :ghpull:`29281`: Port requirements to PEP735 +* :ghpull:`31347`: FIX: Deprecate using clabel() with filled contours +* :ghpull:`31349`: DOC: Correct a few typos in documentation +* :ghpull:`31244`: PERF: Sticky edges speedup +* :ghpull:`31306`: [MNT]: Implement ``Scale.val_in_range`` and refactor ``_point_in_data_domain`` +* :ghpull:`31291`: text: Use font metrics to determine line heights +* :ghpull:`30900`: Added Turbo License doc +* :ghpull:`31307`: FIX: avoid applying dashed patterns to zero-width lines and patches +* :ghpull:`31338`: MAINT: Fix formatting on autoclose bot message +* :ghpull:`31313`: Fixed lingering bugs with image rendering related to exact half display pixels +* :ghpull:`31329`: DOC: Add note about opening multiple PRs +* :ghpull:`29093`: Add wasm CI +* :ghpull:`31283`: MNT: Add autoclose bot inspired by scikit-learn +* :ghpull:`31322`: DOC: fix pcolormesh doc +* :ghpull:`31308`: DOC: Add thumbnail for multipage_pdf gallery example +* :ghpull:`31315`: [BUG] Warn when legend() receives mismatched handles and labels in 2-argument positional form +* :ghpull:`31251`: Emit xlim_changed / ylim_changed when limits expand via set_xticks / set_yticks +* :ghpull:`31316`: DOC: clarify explanation of axline in infinite lines example +* :ghpull:`31309`: DOC: update pandas intersphinx mapping +* :ghpull:`31281`: Drop axis_artist tickdir image compat, due to text-overhaul merge. +* :ghpull:`31294`: MNT: Restrict webagg toolbar actions to valid actions +* :ghpull:`31282`: SEC: Block shell escapes in latex and ps commands +* :ghpull:`31252`: DOC: Fix rendering of quiver documentation +* :ghpull:`31285`: ENH: Ignore empty text for tightbbox +* :ghpull:`31230`: API: Raise ValueError in subplots if num refers to existing figure +* :ghpull:`31133`: fix: resolve FigureCanvasTkAgg clipping on Windows HiDPI +* :ghpull:`30908`: mathtext support for \phantom, \llap, \rlap for faking text metrics. +* :ghpull:`31261`: Bump the actions group with 2 updates +* :ghpull:`30369`: Support standard tickdir control (in/out/inout) in axisartist. +* :ghpull:`27987`: qhull: Fix inconsistent formatting function arguments +* :ghpull:`31061`: BUG: Fix text appearing far outside valid axis scale range +* :ghpull:`31117`: Clarify introductory description in scatter_star_poly example. +* :ghpull:`31203`: Fix Axes.hist crash for numpy timedelta64 inputs +* :ghpull:`31262`: DOC: Correct ``byweekday`` description in ``WeekdayLocator`` +* :ghpull:`31260`: MNT: Raise NotImplementedError for 3D semilog plots +* :ghpull:`31143`: Deprecate public access to XMLWriter; simplify some attribute settings +* :ghpull:`31258`: DOC: Document that set_aspect applies the aspect lazily +* :ghpull:`31005`: PERF: Bezier root finding speedup +* :ghpull:`30980`: Fix 3D axes to properly support non-linear scales (log, symlog, etc.) +* :ghpull:`30844`: allow passing a function to ``CallbackRegistry.disconnect_func`` +* :ghpull:`30995`: PERF: Speed up ticks processing when not visible or using a NullLocator +* :ghpull:`31128`: Fix relim() ignoring scatter PathCollection offsets +* :ghpull:`31166`: Add private Artist-level autoscale participation flag +* :ghpull:`31238`: CI: Explicitly define CI workflow permissions +* :ghpull:`31228`: Bump the actions group with 3 updates +* :ghpull:`29469`: MNT: Separate property cycle handling from _process_plot_var_args +* :ghpull:`31121`: mathtext: add mathnormal and distinguish between normal and italic family +* :ghpull:`31170`: Cleanup QuiverKey init and deprecate some attributes. +* :ghpull:`31004`: PERF: More speedups +* :ghpull:`31226`: ft2font: Read more entries from OS/2 font table +* :ghpull:`31191`: TST: Switch mathtext tests to mpl20 +* :ghpull:`31231`: DOC: make nightly download command one line so it works on Windows +* :ghpull:`30754`: MNT: Improve Grouper +* :ghpull:`31236`: DOC: Remove gitter links and direct folks to Discourse chat +* :ghpull:`31145`: ENH: Snap 3D view angle changes when holding Control key +* :ghpull:`31179`: Remove mpl.text._get_textbox. +* :ghpull:`31202`: ENH: Adds ``errorbar.capthick`` and ``errorbar.elinewidth`` to mplstyle +* :ghpull:`31222`: DOC: Rewrite tickabel rotation example to use rotation_mode +* :ghpull:`31001`: PERF: Text handling speedups +* :ghpull:`30975`: Use LOCALAPPDATA for config/cache directories on Windows +* :ghpull:`30795`: Fix array alpha to multiply (not replace) existing RGBA alpha +* :ghpull:`31021`: Fixed inaccurate image placement and even more resampling bugs +* :ghpull:`31110`: mathtext: Fetch quad width & axis height from font metrics +* :ghpull:`31193`: DOC: Clarify computed_zorder applies to Collections and Patches only +* :ghpull:`31217`: DOC: use pivot='middle' instead of 'mid' in quiver demo +* :ghpull:`31212`: DOC: discourage pivot='mid' for quiver +* :ghpull:`31204`: Reword the "fully-new contributor" section. +* :ghpull:`31201`: DOC: Add sections to rcParams documentation +* :ghpull:`31196`: DOC: Document which files need to be updated for new rcparams +* :ghpull:`31163`: DOC: update new contributor guidance re timelines, AI, reaching out +* :ghpull:`31124`: MAINT: add AI disclosure to pr template +* :ghpull:`31076`: Avoid using pyplot for check_figures_equal +* :ghpull:`31189`: Bump the actions group with 2 updates +* :ghpull:`31188`: Remove use of the discouraged plt.imread() in the docs. +* :ghpull:`31007`: TST: Skip tests that use a large amount of memory by default +* :ghpull:`30967`: ENH: Implement gapcolor for patch edges +* :ghpull:`31142`: doc: explain that gfi is for training and add no AI policy +* :ghpull:`31137`: TST: Simplify image testing decorator calls +* :ghpull:`31119`: MNT: Normalize internal set_foreground calls to RGBA +* :ghpull:`31107`: Fix confusion between text height and ascent in metrics calculations. +* :ghpull:`31168`: Fix docstring ``lib/matplotlib/pyplot.py`` and related ``lib/matplotlib/__init__.py`` +* :ghpull:`31167`: Copy-edit the transform tutorial. +* :ghpull:`31160`: Bump the actions group across 1 directory with 4 updates +* :ghpull:`29374`: DOC: Emphasize artist as annotation in AnnotationBbox demo and add to annotation guide +* :ghpull:`31151`: Add mlx support +* :ghpull:`31141`: Fix mutable default arguments in backend_svg.py +* :ghpull:`31140`: DOC: Document set_figure() is a low-level API +* :ghpull:`31026`: DOC: Explicitly prohibit bots/agents to post contents +* :ghpull:`31131`: MAINT: added don't solve AI note to gfi bot +* :ghpull:`31043`: MAINT: new contributor bot ask for AI usage +* :ghpull:`30803`: {Radio,Check}Buttons: Add 2D grid labels layout support +* :ghpull:`31111`: Remove some code for compatibility with pyparsing<3 +* :ghpull:`31046`: Implement TeX's fraction and script alignment +* :ghpull:`31085`: Refactor RendererAgg.draw_{mathtext,text,tex} to use same base algorithm +* :ghpull:`28814`: patheffects.SimpleLineShadow calling non-existent get_foreground method from GraphicsContextBase +* :ghpull:`31090`: MAINT: Move to first-contribution action +* :ghpull:`31069`: Fix positioning of wide mathtext accents. +* :ghpull:`30938`: Update bundled FreeType and HarfBuzz libraries +* :ghpull:`31091`: BUG: Fix IndexLocator.tick_values returning values greater than vmax +* :ghpull:`31050`: ft2font: Extend OS/2 table with new fields +* :ghpull:`30039`: Rasterize dvi files without dvipng. +* :ghpull:`31081`: Switch from pre-commit to prek +* :ghpull:`30993`: PERF: Speed up log and symlog scale transforms +* :ghpull:`31082`: MNT: Rename check_getitem to getitem_checked +* :ghpull:`31080`: DOC: Fix missing references for updated FT2Font.set_text +* :ghpull:`30746`: Fix PDF bloat for off-axis scatter with per-point colors +* :ghpull:`31062`: Bump the actions group across 1 directory with 4 updates +* :ghpull:`31063`: Merge main back into text-overhaul branch +* :ghpull:`31056`: Keep mathtext boxes in xywh representation throughout. +* :ghpull:`31060`: MNT: Remove unused eventson context from artist property update +* :ghpull:`31059`: PERF: Refactor bezier poly coefficient calcs for speedup +* :ghpull:`31000`: PERF: Skip kwargs normalization in Artist._cm_set +* :ghpull:`31028`: DOC: Generate rcParams docs directly during build +* :ghpull:`31058`: TST: add basic test for set +* :ghpull:`31057`: DOC: Clarify Artist.set() behavior +* :ghpull:`31041`: Add tests for invalid properties and duplicate aliases in Artist.set +* :ghpull:`30978`: MNT: Discourage Artist.update +* :ghpull:`31016`: Doc: Clarify default levels behavior in contour/contourf +* :ghpull:`31031`: RadioButtons: fix self._clicked method (followup to #30997) +* :ghpull:`30059`: Drop the FT2Font intermediate buffer. +* :ghpull:`31013`: docs: improve contour docstring and wrap long lines +* :ghpull:`31044`: fix for sphinx_gallery < 0.16.0 +* :ghpull:`31033`: Add type hint for fig_kw in subplots +* :ghpull:`31030`: DOC: bring the credits page a little more up-to-date +* :ghpull:`31034`: DOC: Make grammatical corrections to documentation +* :ghpull:`30752`: Improving error message for width and position type mismatch in violinplot +* :ghpull:`31023`: Speedup normalize_kwargs by storing aliases in a more practical format. +* :ghpull:`31014`: TST: Fix warnings from Pillow for unavailable features +* :ghpull:`30935`: FIX: Handle AxesWidget cleanup after failed init +* :ghpull:`31020`: DOC: Fix doc builds with Sphinx 9 +* :ghpull:`31025`: DOC: move doc build options into tables and tabs +* :ghpull:`31024`: Fix formatting: add space after # in TODO comment +* :ghpull:`30997`: widgets: use a shared _Buttons class for {Radio,Check}Buttons +* :ghpull:`31010`: DOC: update and slightly reorg docs docs +* :ghpull:`31011`: Fix grammar: 'it would better' -> 'it would be better' in comment +* :ghpull:`31002`: Remove outdated notion of property alias priority from docs. +* :ghpull:`29881`: feat(CI): add Codecov Test Analytics for flaky and failed tests +* :ghpull:`30999`: Bump the actions group across 1 directory with 2 updates +* :ghpull:`30991`: Improve findfont cache invalidation. +* :ghpull:`30992`: Fix typo: remove extra space in MultiCursor deprecation message +* :ghpull:`30984`: DOC: update interactive rebase instructions +* :ghpull:`27946`: Add support for horizontal CheckButtons +* :ghpull:`30778`: MNT: remove decorator frames from traceback +* :ghpull:`30838`: Do not fail when markers are numpy integers +* :ghpull:`30977`: Revert exception handling case after numpy minver bump to 1.25 +* :ghpull:`30849`: Fix Axes.grid() to respect alpha in color tuples +* :ghpull:`30939`: DOC: Improve widgets API documentation +* :ghpull:`30970`: DOC: Move spectral plot examples from lines to statistics +* :ghpull:`30945`: Prevent blitting errors after canvas swap in RadioButtons and CheckButtons +* :ghpull:`30184`: Fixed several accuracy bugs with image resampling +* :ghpull:`30973`: DOC: modernise barh example +* :ghpull:`30956`: DOC: Some small additions to the API docs +* :ghpull:`30959`: DOC: Clarify matplotlib vs. matplotlib-base in conda +* :ghpull:`30950`: TST: account for flakiness with Numpy v1 +* :ghpull:`30954`: Fix trivial typo in example. +* :ghpull:`30947`: TST: always force the SETUPTOOLS_SCM version in test subprocesses +* :ghpull:`30949`: Add uv.lock to .gitignore +* :ghpull:`30948`: DOC: Improve linkage between rcParams-related documentation +* :ghpull:`30871`: Define the supported rcParams as code +* :ghpull:`30886`: BUG: Fix Windows subprocess timeouts with CREATE_NO_WINDOW flag +* :ghpull:`30777`: DOC: Introduce backend versions +* :ghpull:`30824`: Fixed bilinear interpolation for ``SegmentedBivarColormap`` +* :ghpull:`30942`: Bump pypa/cibuildwheel from 3.3.0 to 3.3.1 in the actions group +* :ghpull:`30918`: TST: account for asyncio changes in py314 +* :ghpull:`30937`: Merge branch 'v3.10.x' into main +* :ghpull:`30936`: DOC: Clarify data inputs for boxplot() and violinplot() +* :ghpull:`30855`: DOC: Clarify and unify set_linestyle +* :ghpull:`30921`: Exclude confirmed bugs from stale bot +* :ghpull:`30892`: Bump the actions group across 1 directory with 11 updates +* :ghpull:`30920`: FIX: Increase reruns for flaky test_invisible_Line_rendering (#30809) +* :ghpull:`30889`: MNT: Make transforms helper functions private +* :ghpull:`30922`: Reduce stale bot to run once per week +* :ghpull:`30912`: Pcolormesh Doc Fix +* :ghpull:`30916`: Docs: Remove outdated annotate_transform example, link to annotation tutorial +* :ghpull:`30919`: DOC: Correct typos on a/an usage including print messages +* :ghpull:`30914`: Fix outdated documentation links for violin/boxplot example +* :ghpull:`30907`: Inline intermediate constructs in axisartist demos. +* :ghpull:`30867`: Handle single color for multiple datasets in ``hist`` +* :ghpull:`30591`: FIX: Make widget blitting compatible with swapped canvas +* :ghpull:`30821`: Implements the Okabe-Ito accessible colormap. +* :ghpull:`30737`: Deprecate unused canvas parameter to MultiCursor +* :ghpull:`29966`: Fix AxesWidgets on inset_axes that are outside their parent. +* :ghpull:`30600`: Implement warning for Text3D's rotation/rotation_mode parameters +* :ghpull:`30847`: Fix test_ensure_multivariate_data on 32-bit systems +* :ghpull:`30856`: DOC: Rectangle: Link to FancyBboxPatch for rounded corners +* :ghpull:`30854`: DOC: Improve docs of legend loc=best +* :ghpull:`30863`: Fix macOS toolbar crash +* :ghpull:`30853`: Minor doc fixes re: close()ing figures. +* :ghpull:`30846`: Add pixi and uv install options to bug template +* :ghpull:`30842`: Update release docs for new publish workflow, remove old publish step +* :ghpull:`30841`: Add type annotation for LocationEvent.modifiers +* :ghpull:`30775`: FIX: figureoptions updates title string only +* :ghpull:`30726`: Enh/Add hatch pattern support to Axes.grouped_bar +* :ghpull:`30808`: Consolidate style parameter handling for plotting methods that call other plotting methods +* :ghpull:`30815`: MNT: Fix handling of ints in rgb_to_hsv() +* :ghpull:`30533`: gtk: Add more explicit version requirements +* :ghpull:`30835`: Improve error messages for mismatched s arg to scatter(). +* :ghpull:`30750`: FIX: when creating a canvas from a Figure use original dpi +* :ghpull:`30822`: DOC: Define the effect of rcParams["figure.raise_window"] = False +* :ghpull:`30052`: Setting imshow(animated=True) still show does not show an image +* :ghpull:`30820`: DOC: Add parameters documentation for FFMpegFileWriter +* :ghpull:`30816`: Fix typos in API interfaces documentation +* :ghpull:`30814`: DOC: Discouraged duplicate colormaps +* :ghpull:`30813`: Add legend.linewidth to rcParam type hint +* :ghpull:`30705`: Add testing for rcParams Literal type hints +* :ghpull:`30812`: DOC: remove duplicate whatsnew heading +* :ghpull:`30810`: Fix rstcheck failures +* :ghpull:`30334`: Add support for loading all fonts from collections +* :ghpull:`30760`: Fix axis3d to include offset text in tight bounding box calculation +* :ghpull:`30780`: Add legend.linewidth parameter to control legend box edge linewidth +* :ghpull:`30799`: DOC: don't index or unpack the return value of pie +* :ghpull:`30766`: Fix colorbar alignment with suptitle in compressed layout mode +* :ghpull:`30756`: Add legend support for PatchCollection +* :ghpull:`30782`: DOC: Reintroduce glossary +* :ghpull:`29494`: github: added explicit do not merge label to label check +* :ghpull:`30784`: correct statement about available methods in ``Quiver`` docstring +* :ghpull:`30733`: ENH: introduce PieContainer and pie_label method +* :ghpull:`30783`: DOC: Add example usage to make_keyword_only() +* :ghpull:`30776`: MNT: Declare table() to be not further developed +* :ghpull:`30774`: DOC: Fix documentation error of hexbin +* :ghpull:`30607`: Implement libraqm for vector outputs +* :ghpull:`30753`: Update mpl-sphinx-theme in environment.yml +* :ghpull:`30699`: [DOC] dev landing page admonition about AI usage/link to policy +* :ghpull:`30761`: DOC: Clarify restrictions on GenAI usage +* :ghpull:`30724`: Bump github/codeql-action from 4.31.0 to 4.31.2 in the actions group +* :ghpull:`30665`: Grammar corrections in User guide FAQ +* :ghpull:`30741`: Add :code-caption: option to plot directive +* :ghpull:`30736`: DOC: Correct grammatical issues especially on a/an usage +* :ghpull:`30627`: Remove forced fallback from FT2Font::load_char +* :ghpull:`30715`: Fix spacing in r"$\max f$". +* :ghpull:`30723`: Add file extension to whatsnew entry +* :ghpull:`30690`: Bump the actions group with 3 updates +* :ghpull:`30560`: Consistent zoom boxes +* :ghpull:`30565`: fix: Qt5Agg support darkmode icon by using svg +* :ghpull:`29989`: fix: Fix unstable tkagg small plot size. +* :ghpull:`30708`: doc: make external scipy link explicit +* :ghpull:`30511`: Update Colorizer/ColorizingArtist to work with MultiNorm +* :ghpull:`30696`: FIX: Account for horizontal/vertical lines in tightbox +* :ghpull:`30316`: Create RCKeyType +* :ghpull:`30686`: DOC: Remove notebook instructions from image tutorial +* :ghpull:`30684`: Update README links to static images +* :ghpull:`30640`: Bump the actions group across 1 directory with 6 updates +* :ghpull:`30677`: Merge branch 'main' into text-overhaul +* :ghpull:`30668`: cibw: Switch macos 13 to 15 Intel +* :ghpull:`30667`: DOC: Correct typos: lets -> let's [ci docs] +* :ghpull:`28831`: Improve the cache when getting font metrics +* :ghpull:`30655`: simplify ContourSet.draw +* :ghpull:`30652`: Stale action: sort issues by last updated +* :ghpull:`30636`: FIX: Keep legacy alpha behavior for violinplot without facecolor +* :ghpull:`30646`: merge up v3.10.7 +* :ghpull:`30639`: DOC: Add note about linear colorbar scale option for TwoSlopeNorm +* :ghpull:`30629`: Fix test_mult_norm_call_types on 32-bit systems +* :ghpull:`30634`: Don't force axes limits in hist2d. +* :ghpull:`29221`: Multivariate plotting in imshow, pcolor and pcolormesh +* :ghpull:`30630`: Update first-interaction from v3.0.0 to v3.1.0 +* :ghpull:`29695`: Add font feature API to Text +* :ghpull:`30608`: Prepare ``CharacterTracker`` for advanced font features +* :ghpull:`30531`: MNT: Pending-deprecate setting colormap extremes in-place +* :ghpull:`30543`: ENH: support x/y-axis zoom +* :ghpull:`30590`: MNT: Define Protocol for Animation.event_source +* :ghpull:`30619`: Include step info in str(scroll_event). +* :ghpull:`30620`: Add --debug flag to python -mmatplotlib.dviread CLI. +* :ghpull:`30499`: Improve cursor icons with RectangleSelector +* :ghpull:`30610`: Bump mpl-sphinx-theme version +* :ghpull:`30615`: Use auto to remove long typedefs in dlsym/GetProcAddress calls. +* :ghpull:`30616`: DOC: add what's new info for violin_stats +* :ghpull:`30606`: DOC: Fix raw string in mathtext unicode example +* :ghpull:`30603`: MNT: Fix some broken deprecations +* :ghpull:`30512`: pdf: Improve text with characters outside embedded font limits +* :ghpull:`29936`: Fix auto-sized glyphs with BaKoMa fonts +* :ghpull:`30573`: Add os.PathLike support to FT2Font constructor, and FontManager +* :ghpull:`30595`: ft2font: Split layouting from set_text +* :ghpull:`30596`: Cleanup donuts example. +* :ghpull:`29794`: Add language parameter to Text objects +* :ghpull:`30583`: MNT: Streamline deferred initialization of Colormap +* :ghpull:`30582`: MNT: Do not use colormap setters in tests +* :ghpull:`30567`: pdf: Merge loops for single byte text chunk output +* :ghpull:`30579`: Merge main back into text-overhaul branch to fix CI +* :ghpull:`30586`: ci: Bump Ubuntu ARM builder to 24.04 +* :ghpull:`30581`: TST: Force Agg backend in test_openin_any_paranoid +* :ghpull:`30569`: Copy-edit the "fonts in pdf and postscript" table. +* :ghpull:`30208`: Make path extension a bit safer +* :ghpull:`30577`: MNT: Move all Colormap extremes setter logic into a single _set_extremes() +* :ghpull:`30562`: DOC: improve description of boilerplate.py +* :ghpull:`30566`: pdf/ps: Track full character map in CharacterTracker +* :ghpull:`30335`: Use glyph indices for font tracking in vector formats +* :ghpull:`30561`: Bump github/codeql-action from 3.30.1 to 3.30.3 in the actions group +* :ghpull:`29855`: ENH: Allow to register standalone figures with pyplot +* :ghpull:`29742`: DOC: Explain how to start the mainloop after show(block=False) +* :ghpull:`29502`: CI: remove xfail on OSX + tk due to issues in image +* :ghpull:`30514`: Prepare for MetaFont/PK font support. +* :ghpull:`30536`: DOC: Cleanup/restructure PR guidelines +* :ghpull:`30405`: ENH: Scroll to zoom +* :ghpull:`30530`: Bump the actions group across 1 directory with 10 updates +* :ghpull:`30532`: MNT: Change default name of ListedColormaps +* :ghpull:`30535`: Fix: pytest warning - GioUnix was imported without specifying version +* :ghpull:`30520`: pdf: Simplify Type 3 font character encoding +* :ghpull:`30387`: MNT: Refactor default violin KDE estimator +* :ghpull:`30462`: FIX: Mark shared Axes as stale when propagating adjustable +* :ghpull:`30507`: DOC: Clarify draft PR and move from ways to contribute to PR guidelines +* :ghpull:`30465`: removed test_image_cursor_formatting() +* :ghpull:`29939`: Parse {lua,xe}tex-generated dvi in dviread. +* :ghpull:`30510`: Update syntax for PR welcome workflow +* :ghpull:`30000`: Implement text shaping with libraqm +* :ghpull:`30408`: MNT/DOC: Deprecate anchor in Axes3D.set_aspect +* :ghpull:`30491`: merge up v3.10.6 +* :ghpull:`30475`: Fix spelling error in ``contains_branch_separately`` method name +* :ghpull:`30505`: Add Linux Foundation Health Score badge to README +* :ghpull:`30423`: Fix Line3DCollection with autolim=True for lines of different lengths +* :ghpull:`30479`: Clarify inset_locator.inset_axes demo. +* :ghpull:`30467`: Let ticklabels respect set_in_layout(False). +* :ghpull:`30478`: MNT: correct _replacer docstring +* :ghpull:`30471`: DOC: Fix text formatting of imshow_extent example +* :ghpull:`30469`: Deprecate redundant axes parameter to RadialLocator. +* :ghpull:`30384`: Add datetime test for ax.violin +* :ghpull:`30470`: No need to sanitize extrema in Colorizer.set_clim +* :ghpull:`30468`: Let triage_tests support test modules with only figure_equals tests. +* :ghpull:`30433`: Use standard property alias machinery in contour(). +* :ghpull:`30459`: DOC: simplify hat graph example +* :ghpull:`30456`: DOC: Correct a typo: confuzzlment -> confuzzlement +* :ghpull:`30455`: DOC: Fix typo in axes docstring +* :ghpull:`30454`: Added handling for undetermined home directory +* :ghpull:`30453`: DOC: Fix missing references on text-overhaul branch +* :ghpull:`30401`: merge up v3.10.5 +* :ghpull:`30452`: DOC: Move capture_scroll What's new note to new directory +* :ghpull:`30403`: Add scroll capture functionality to WebAgg backend +* :ghpull:`29876`: MultiNorm class +* :ghpull:`30446`: Added hardcoded colormap attributes for type checker support +* :ghpull:`30441`: Bump github/codeql-action from 3.29.8 to 3.29.10 in the actions group +* :ghpull:`30328`: Fix legend ``labelcolor=‘linecolor’`` to handle various corner cases, e.g. step histograms and transparent markers +* :ghpull:`30440`: Document relative font sizes +* :ghpull:`30402`: Update release guide +* :ghpull:`30031`: merge up 3.10.3 +* :ghpull:`30425`: Remove outdated reference to matplotlibbaselinemarker in tex sources. +* :ghpull:`29358`: MNT: Registered 3rd party scales do not need an axis parameter anymore +* :ghpull:`30422`: DOC: remove some usages of None as explicit defaults +* :ghpull:`30304`: Move release related docs to new sub-folder +* :ghpull:`30416`: Bump the actions group across 1 directory with 7 updates +* :ghpull:`30404`: DOC: Scale axis parameter +* :ghpull:`30324`: Make PyFT2Font a subclass of FT2Font +* :ghpull:`30362`: {,Range}Slider: accept callable valfmt arguments +* :ghpull:`30226`: ENH: Add properties bottoms, tops, and position_centers to BarContainer +* :ghpull:`30398`: TST: Remove qt_core fixture +* :ghpull:`30396`: Fix the link to latest stable documentation +* :ghpull:`30382`: MNT: Remove explicit use of default value add_collection(..., autolim=True) +* :ghpull:`30383`: DOC: Simplify Line, Poly and RegularPoly example +* :ghpull:`29958`: ENH: ax.add_collection(..., autolim=True) updates view limits +* :ghpull:`30374`: TST: Make determinism test plots look less pathological +* :ghpull:`29716`: ENH: Add align parameter to broken_barh() +* :ghpull:`30284`: Fixed the overdeletion of source images for failing tests +* :ghpull:`30348`: Keep default minor log ticks if there's 1 major & 1 minor tick. +* :ghpull:`30273`: Fix mlab fallback for 32-bit systems +* :ghpull:`30143`: TYP: Make glyph indices distinct from character codes +* :ghpull:`29465`: ENH: Type the possible str legend locs as Literals +* :ghpull:`30375`: Fix highlighting of install docs. +* :ghpull:`30376`: Shorten setup of axes in simple_axis_pad demo. +* :ghpull:`30367`: Support passing xticks/yticks when constructing secondary_axis. +* :ghpull:`30368`: Switch get_grid_info to take a single Bbox as parameter. +* :ghpull:`29993`: Trigger events via standard callbacks in widget testing. +* :ghpull:`30363`: Register 'avif' format when available in Pillow +* :ghpull:`29890`: Show subprocess stdout and stderr on pytest failure +* :ghpull:`30373`: Mnt/test qol improvements +* :ghpull:`30359`: ENH: Allow tuple for borderpad in AnchoredOffsetbox +* :ghpull:`30366`: Cross-ref the two-scales and secondary-axes examples. +* :ghpull:`30349`: Axes can't set navigate_mode. +* :ghpull:`30347`: Small cleanups. +* :ghpull:`30322`: Deprecate setting text kerning factor to any non-None value +* :ghpull:`30332`: CI: Harden GHA configuration +* :ghpull:`30346`: MNT: Fix isort line length setting +* :ghpull:`30314`: [MNT] Typing: correct typing overloads for ````Figure.subfigures```` +* :ghpull:`30343`: Fix broken/deprecated documentation links in MEPs and testing guides +* :ghpull:`30330`: [fix] Spine.set_bounds() does not take parameter **None** as expected +* :ghpull:`30339`: MNT: Prefer capitalized logging levels +* :ghpull:`30340`: Bump the actions group with 2 updates +* :ghpull:`30302`: [MNT] Typing: Use Literal for set_loglevel +* :ghpull:`30001`: Include close matches in error message when key not found +* :ghpull:`30333`: FIX: cast Patch linewidth to float for dash scaling +* :ghpull:`30329`: Deprecate font_manager.is_opentype_cff_font +* :ghpull:`25573`: FIX: be very paranoid about checking what the current canvas is +* :ghpull:`30319`: Don't set a default size for FT2Font +* :ghpull:`29816`: Update FreeType to 2.13.3 +* :ghpull:`30317`: fix broken configobj link +* :ghpull:`30261`: [TYP] Add more literals to MarkerType +* :ghpull:`30312`: Replace deprecated imports +* :ghpull:`30315`: Fix link to pango +* :ghpull:`30272`: Log a warning if selected font weight differs from requested +* :ghpull:`30311`: Bump the actions group with 2 updates +* :ghpull:`30309`: Improve custom sphinx link redirect extension +* :ghpull:`30174`: FIX: Ensure Locators on RadialAxis are always correctly wrapped +* :ghpull:`30281`: Fix several minor typos +* :ghpull:`30275`: Create events type and update plt.connect and mpl_connect +* :ghpull:`30279`: fix(config): Correct invalid value for svg.fonttype in matplotlibrc +* :ghpull:`30134`: Add typing to AFM parser +* :ghpull:`30274`: ci: Fix image preload with multiple conflicts +* :ghpull:`30231`: ci: Preload existing test images from text-overhaul-figures branch +* :ghpull:`29115`: Use old stride_windows implementation on 32-bit builds +* :ghpull:`30235`: Don't expose private styles in style.available +* :ghpull:`30266`: DOC: fix artist see also sections +* :ghpull:`30258`: Clean up mypy & ruff config +* :ghpull:`30262`: Tweak docstrings of get_window_extent/get_tightbbox. +* :ghpull:`30239`: Upgrade to Visual Studio 2022 in appveyor.yml +* :ghpull:`30245`: Adjust logic in RcParams to allow for inheritance +* :ghpull:`30232`: Bump github/codeql-action from 3.29.0 to 3.29.2 in the actions group +* :ghpull:`30196`: agg: Replace facepair_t with std::optional +* :ghpull:`30200`: Add explicit signatures for pyplot.{polar,savefig,set_loglevel} +* :ghpull:`30178`: Abstract base class for Normalize +* :ghpull:`30220`: BUG: Include python-including headers first in src/ft2font.{cpp,h} +* :ghpull:`30199`: Add explicit getter / setter overloads for pyplot.{xlim,ylim} +* :ghpull:`30202`: Add explicit overloads for pyplot.{show,subplot} +* :ghpull:`29988`: Refactoring: Removing axis parameter from scales +* :ghpull:`30082`: Simplify dviFontInfo layout in backend pdf. +* :ghpull:`30163`: Prepare to turn matplotlib.style into a plain module. +* :ghpull:`30206`: Use collections.deque to store animation cache data. +* :ghpull:`29481`: Support individual styling of major and minor grid through rcParams +* :ghpull:`28764`: Fix argument types in examples and tests +* :ghpull:`30197`: DOC: Remove last userdemo example +* :ghpull:`30191`: Simplify RendererAgg::draw_markers buffers +* :ghpull:`30188`: Fixed incomplete deletion of all images that have passed tests before upload +* :ghpull:`30168`: Remove fallback code for glyph indices +* :ghpull:`29102`: TST: Calculate RMS and diff image in C++ +* :ghpull:`30145`: Remove ttconv backwards-compatibility code +* :ghpull:`30181`: Bump the actions group with 3 updates +* :ghpull:`28187`: Add a filename-prefix option to the Sphinx plot directive +* :ghpull:`30154`: Bump github/codeql-action from 3.28.18 to 3.28.19 in the actions group +* :ghpull:`30054`: Fixed an off-by-half-pixel bug in image resampling when using a nonaffine transform (e.g., a log axis) +* :ghpull:`30150`: Update font-related documentation +* :ghpull:`29199`: Fix center of rotation with rotation_mode='anchor' +* :ghpull:`30153`: Throw exception when alpha is out of bounds +* :ghpull:`30151`: Fix typo in backend_ps.py comment: change 'and them scale them' to 'and then scale them' +* :ghpull:`30107`: Add example to histogram colorbar on galleries +* :ghpull:`20716`: Type-1 font subsetting +* :ghpull:`30067`: Remove deprecations: is_bbox and more +* :ghpull:`28560`: ENH: Add grouped_bar() method +* :ghpull:`30137`: BLD: Remove FreeType from Agg backend extension +* :ghpull:`29392`: Fill hatch in PDF backend +* :ghpull:`30130`: Make NavigationToolbar.configure_subplots return value consistent +* :ghpull:`30132`: DOC: Clarify that types in docstrings do not use formal type annotation syntax +* :ghpull:`30131`: DOC: Document the properties of Normalize +* :ghpull:`30112`: Update to docs with regards to colorbar and colorizer +* :ghpull:`30004`: Remove apply_theta_transforms argument +* :ghpull:`30070`: Deprecate point_at_t and document that a BezierSegment can be called +* :ghpull:`30121`: Clean up AFM code +* :ghpull:`30123`: Fix FT_CHECK compat with macOS 10.15 +* :ghpull:`30088`: Parse FontBBox in type1font. +* :ghpull:`30099`: Fix tight-bbox computation of HostAxes. +* :ghpull:`30102`: Simplify/improve error reporting from ft2font. +* :ghpull:`30113`: Bump scientific-python/circleci-artifacts-redirector-action from 1.0.0 to 1.1.0 in the actions group +* :ghpull:`30100`: Use fix-cm instead of type1cm. +* :ghpull:`30109`: DOC: expand petroff10 example to include 6- and 8- styles +* :ghpull:`30044`: Replace FT2Image by plain numpy arrays. +* :ghpull:`30097`: remove point troubling regex +* :ghpull:`30090`: Simplify some Sphinx tests +* :ghpull:`30061`: Move test data into a single subdirectory +* :ghpull:`30085`: DOC: add API docs content guidelines to api docs instructions +* :ghpull:`30084`: DOCS: add plot types content guidance to docs +* :ghpull:`30087`: DOC: Add petroff6 and petroff8 to 'Named color sequences' example +* :ghpull:`30080`: Bump the actions group with 3 updates +* :ghpull:`30065`: ENH: Add Petroff 6 and 8 color cycle style sheets +* :ghpull:`30077`: Fix deprecated attribute name in backend_pdf. +* :ghpull:`30069`: Close star polygons +* :ghpull:`30062`: Add 3D scatter test for cmap update +* :ghpull:`30066`: Remove get_bbox_header +* :ghpull:`30045`: CI: try running the precommit hooks on GHA +* :ghpull:`29910`: DOC: add warnings about get_window_extent and BboxImage +* :ghpull:`30032`: Add Matplotlib Journey online course to external resources +* :ghpull:`30055`: Renamed an RST file to remove a leading space in its filename +* :ghpull:`30049`: DOC: consolidate version switcher guidance +* :ghpull:`30050`: DOC: Additional tip to exclude undesired matches in GitHub code search +* :ghpull:`30005`: Remove cm.get_cmap +* :ghpull:`30048`: DOC: version switcher update on release +* :ghpull:`30047`: Update version switcher for 3.10.3 +* :ghpull:`30036`: Remove cutout for missing font file in PdfFile._embedTeXFont. +* :ghpull:`29847`: ci: restrict 'pygobject-ver' for Ubuntu 22.04 jobs +* :ghpull:`30030`: Add "sans" alias to rc() to allow users to set font.sans-serif +* :ghpull:`30040`: Improve usetex and pgf troubleshooting docs. +* :ghpull:`30037`: Update top message matplotlibrc file +* :ghpull:`30035`: Remove meson-python pinning +* :ghpull:`30006`: Enable linting of .pyi files +* :ghpull:`30020`: Micro-optimize _to_rgba_no_colorcycle. +* :ghpull:`30027`: Make PdfFile font-related attributes private. +* :ghpull:`29829`: Rework mapping of dvi glyph indices to freetype indices. +* :ghpull:`30023`: Remove unused ``_api`` import +* :ghpull:`30014`: Remove deprecated get_tick_iterator() +* :ghpull:`30015`: Expire deprecation of nth_coord arguments +* :ghpull:`30019`: FIX #30007: Raise ValueError when all wedge sizes are zero in ax.pie +* :ghpull:`30016`: Bump github/codeql-action from 3.28.16 to 3.28.17 in the actions group +* :ghpull:`30003`: DOC: missing word + add latex dep section +* :ghpull:`29341`: Type annotation add_subplot for projection="3d" +* :ghpull:`29764`: added latex requirements from fedora spec +* :ghpull:`29918`: DOC: Add descriptions to matplotlib.typing +* :ghpull:`27576`: Fix specifying number of levels with log contour +* :ghpull:`29879`: Adding elinestyle property to errorbar +* :ghpull:`29984`: FIX: Typing of FuncAnimation +* :ghpull:`29973`: Use inline lambdas to define most FT2Font properties. +* :ghpull:`29982`: Bump the actions group with 5 updates +* :ghpull:`29972`: Improve repr of mathtext internal structures; minor cleanup. +* :ghpull:`29356`: Add a last resort font for missing glyphs +* :ghpull:`29873`: Handled non finite values in ax.pie - issue #29860 +* :ghpull:`29916`: Bump the actions group with 2 updates +* :ghpull:`27183`: Fix behaviour of Figure.clear() for SubplotParams +* :ghpull:`29954`: Simplify ``colored_line()`` implementation in Multicolored lines example +* :ghpull:`29956`: MNT: make signature of GridSpec.update explicit +* :ghpull:`29203`: Fixed imsave() saving incorrect color map +* :ghpull:`29946`: Changed "Autoscaling axes" to "Autoscaling axes on user guide page for issue & closes #29906 +* :ghpull:`29948`: Check Axes/Figure import paths in boilerplate.py +* :ghpull:`29904`: API: bump minimum supported version of Python and numpy +* :ghpull:`29945`: Doc fixed aspect colorbar +* :ghpull:`29944`: DEV: have ruff check blank-line counts +* :ghpull:`29923`: Fix signature of disabled draw methods +* :ghpull:`29614`: add detail to doc string in Line3DCollection +* :ghpull:`29843`: Fix loading of Type1 "native" charmap. +* :ghpull:`29911`: Bump pre-commit versions +* :ghpull:`29892`: FIX: make_image should not modify original array +* :ghpull:`29905`: Remove hatchcolors parameter from draw_quad_mesh +* :ghpull:`29898`: backend_bases.pyi: ``@overload`` ``FigureCanvasBase.mpl_connect()`` for different event types +* :ghpull:`29745`: Use PEP8 style method and function names from pyparsing +* :ghpull:`29762`: Use ruff instead of flake8 to check PEP8 +* :ghpull:`29885`: Bump github/codeql-action from 3.28.13 to 3.28.14 in the actions group +* :ghpull:`29592`: DOC: Remove simple_legend examples from User Demo +* :ghpull:`29875`: DOC: Improve description of background/bbox handling for Text +* :ghpull:`29612`: ENH: Support units when specifying the figsize +* :ghpull:`29833`: TST: remove (most) text from constrained layout tests +* :ghpull:`29870`: doc: a grammatical error in pyplot comment +* :ghpull:`29831`: Inline _calc_extents_from_path. +* :ghpull:`29851`: Do not extraneously clip 3D plots +* :ghpull:`29846`: ci: cleanup: remove stale/outdated version range restrictions +* :ghpull:`29841`: Bump the actions group with 2 updates +* :ghpull:`29850`: MNT: Use Gcf.destroy(manager) instead of Gcf.destroy(manager.num) +* :ghpull:`29765`: ci: Introduce ubuntu-24.04 to restore GTK test coverage with recent PyGObject versions +* :ghpull:`29838`: Switch Tfm metrics to TrueType-compatible API. +* :ghpull:`29783`: Fix log scaling for pcolor and pcolormesh +* :ghpull:`29832`: MNT: expire legend-related deprecations +* :ghpull:`29044`: Add hatchcolor parameter for Collections +* :ghpull:`29828`: Improve output of dvi debug parsing. +* :ghpull:`29798`: Ensure polar plot radial lower limit remains at 0 after set_rticks + plot +* :ghpull:`29830`: Fix git fetch on development workflow +* :ghpull:`29776`: Filter images in premultiplied alpha mode. +* :ghpull:`29821`: Tweak minimal checks for GUI binding installs. +* :ghpull:`29808`: ENH: set default color cycle to named color sequence +* :ghpull:`29817`: Prepare for {xe,lua}tex support in usetex. +* :ghpull:`27972`: Fix ngrids support in axes_grid.Grid(). +* :ghpull:`29804`: replace quansight-labs/setup-python with actions/setup-python +* :ghpull:`29800`: Bump the actions group with 6 updates +* :ghpull:`29083`: DOC: Update page to note installation for ninja library +* :ghpull:`29698`: Improve tick subsampling in LogLocator. +* :ghpull:`29701`: Bump the actions group across 1 directory with 7 updates +* :ghpull:`28352`: Add compilers to conda environment +* :ghpull:`29696`: ENH: Add support for per-label padding in bar_label +* :ghpull:`29582`: Add ``rasterized`` option to ``contourf`` +* :ghpull:`29759`: DOC: expand use of fun tag +* :ghpull:`29758`: DOC: consolidate tags +* :ghpull:`29756`: Consolidate color tags +* :ghpull:`29747`: Revert "NEP 29 > SPEC 0 in dependency policy" +* :ghpull:`29744`: NEP 29 > SPEC 0 in dependency policy +* :ghpull:`29700`: merge up v3.10.1 +* :ghpull:`26774`: Connect the Animation event source callback in the constructor. +* :ghpull:`29729`: DOC: Improve What's new entry description +* :ghpull:`29718`: Update version switcher for 3.10.1 +* :ghpull:`29602`: MNT: Reduce the use of get_xticklabels() in examples +* :ghpull:`29705`: DOC: improve dev install docs +* :ghpull:`29644`: [Doc] Added images of hatches to hatch API page +* :ghpull:`29697`: MNT: remove ``plot_date`` +* :ghpull:`29690`: Add test cases for patch.force_edgecolor behavior with facecolor="none" +* :ghpull:`29558`: Consolidate align_labels_demo and align_ylabels gallery examples +* :ghpull:`29660`: fix: broken link +* :ghpull:`29639`: Bump the actions group across 1 directory with 7 updates +* :ghpull:`29620`: DOC: Add tip how to use GitHub code search to estimate the impact of a deprecation +* :ghpull:`29613`: doc: add link to analytics page +* :ghpull:`29593`: Fix tick_params() label rotation mode +* :ghpull:`29589`: DOC: Minor example cleanup +* :ghpull:`29580`: DOC: More cleanup of missing-references.json +* :ghpull:`29581`: Use functools.cache instead of lru_cache to establish singletons. +* :ghpull:`29566`: DOC: Remove invalid link in Communication Guide +* :ghpull:`29565`: Remove rcParams deprecation machinery +* :ghpull:`29561`: DOC: Document _CollectionWithSizes +* :ghpull:`29569`: Ignore ImageMagick deprecation of "convert" command. +* :ghpull:`29574`: 3D depthshade what's new plot +* :ghpull:`29052`: FIX: Checks for (value, color) tuples in LinearSegmentedColormap.from_list +* :ghpull:`29556`: Spacing for description of linecolor +* :ghpull:`28784`: Improve fallback font export tests +* :ghpull:`28968`: Implement xtick and ytick rotation modes +* :ghpull:`29450`: Remove some unused resample code +* :ghpull:`29503`: Improve error message for shape mismatches in barh function +* :ghpull:`29553`: DOC: update active social media list +* :ghpull:`27304`: Allow user to specify colors in violin plots with constructor method +* :ghpull:`29287`: Fix depth shading on 3D scatterplots +* :ghpull:`29398`: Speed up Collection.set_paths +* :ghpull:`29525`: Add new method Colormap.with_alpha() +* :ghpull:`29537`: Fix: Ensure ScalarFormatter.set_useOffset properly distinguishes betw… +* :ghpull:`29533`: Minor cleanups. +* :ghpull:`29397`: 3D plotting performance improvements +* :ghpull:`29529`: MNT: Deprecate other capitalization than "None" in matplotlibrc +* :ghpull:`29526`: DOC: better separation of codespace instructions +* :ghpull:`29486`: FIX: Make stem() baseline follow the curvature in polar plots +* :ghpull:`29460`: ENH: Add bad, under, over kwargs to Colormap +* :ghpull:`29435`: Fix ``plot_wireframe`` with nonequal ``rstride``, ``cstride``, plus additional speedups +* :ghpull:`29491`: Bump the actions group across 1 directory with 2 updates +* :ghpull:`29375`: Doc: document pending deprecation procedure +* :ghpull:`29497`: ci: Fix cache key for Matplotlib data +* :ghpull:`29473`: CI: add py312 and py313 on windows on azure to test matrix +* :ghpull:`29477`: ci: Add an ARM Linux test workflow +* :ghpull:`29372`: DOC / BUG: Fix savefig to GIF format with .gif suffix +* :ghpull:`29028`: Update colormap usage documentation to prioritize string colormap names +* :ghpull:`29461`: DOC: Use color specification reference in matplotlib.colors docs +* :ghpull:`29438`: ft2font: Avoid undefined enum values +* :ghpull:`29463`: Fix dead links in dev workflow docs +* :ghpull:`29464`: DOC: Add missing examples for legend outside positions +* :ghpull:`29433`: Remove erroneous statement in multipage PDF example +* :ghpull:`29441`: DOC: Rename Twitter to X +* :ghpull:`29399`: plot_wireframe plotting speedup +* :ghpull:`29325`: Propagate Axes class and kwargs for twinx and twiny +* :ghpull:`29424`: MNT: Turn Axes._axis_map into a static dict instead of a property +* :ghpull:`29427`: BUG: Fix regression with set_hatchcolor +* :ghpull:`29419`: Merge v3.10.x into main +* :ghpull:`29413`: [pre-commit.ci] pre-commit autoupdate +* :ghpull:`29415`: Bump the actions group across 1 directory with 5 updates +* :ghpull:`29338`: Use set_window_title rather than set_label to set title of webagg figure +* :ghpull:`29388`: FIX: get_tick_position() should disregard major/minor ticks that are not drawn +* :ghpull:`27327`: Update for checking whether colors have an alpha channel +* :ghpull:`29405`: DOC: Clearer wording for the installation of external dependencies +* :ghpull:`29402`: Expand set_ticklabels warning +* :ghpull:`29400`: Fix/Suppress more missing references +* :ghpull:`29394`: Tick rendering speedups +* :ghpull:`29386`: MNT: Remove ``*args`` for ``OffsetBox.__init__()`` +* :ghpull:`28104`: Separates edgecolor from hatchcolor +* :ghpull:`29377`: DOC: change wording on new contributor path +* :ghpull:`29376`: API: bump the minimum version of pillow +* :ghpull:`29333`: ENH: Streamplot control for integration max step and error +* :ghpull:`29342`: MNT: Warn on using pixel marker for scatter() +* :ghpull:`29344`: MNT: Coerce LineStyleType strings to Literal +* :ghpull:`29354`: Use _val_or_rc in more places +* :ghpull:`29360`: DOC: update switcher for 3.10 +* :ghpull:`29174`: ``indicate_inset`` transform support +* :ghpull:`27551`: Move axisartist towards untransposed transforms (operating on (N, 2) arrays instead of (2, N) arrays). +* :ghpull:`24714`: Improve handling of degenerate jacobians in non-rectilinear grids. +* :ghpull:`29343`: MNT: Discourage alternate strings for 'none' linestyle +* :ghpull:`29054`: Label log minor ticks if only one log major tick is drawn. +* :ghpull:`29346`: DOC: fix typos +* :ghpull:`29340`: FIX: pass renderer through adjust_bbox +* :ghpull:`29345`: MNT: Remove duplicate assignment +* :ghpull:`29329`: CI: allow pandas install to fail on nightly test run +* :ghpull:`29322`: DOC: Add [*Discouraged*] prefix to summary lines +* :ghpull:`25870`: Adds error handling around install_repl_displayhook +* :ghpull:`29303`: DOC: Enhance documentation on discouraged API +* :ghpull:`29280`: Apply some modernization to C++ extensions +* :ghpull:`23085`: Update art3d.py to address strange behavior of depthshading on 3D scatterplots with close points +* :ghpull:`29215`: added venv to gitignore +* :ghpull:`29257`: fix typo +* :ghpull:`28775`: DOC: manually placing images example +* :ghpull:`29222`: TST: Simplify parts of animation tests +* :ghpull:`29220`: DOC: Set stable version to 3.9.3 +* :ghpull:`29214`: Fix typo in _LazyTickList class comment (lis -> list) +* :ghpull:`29171`: ci: Remove Linux & macOS from Azure +* :ghpull:`29187`: DOC: verify your changes +* :ghpull:`29184`: Minor tweaks to image docs. +* :ghpull:`29172`: Minor cleanups to docstrings, comments, and error messages. +* :ghpull:`29155`: Delay warning for deprecated parameter 'vert' of box and violin +* :ghpull:`27617`: Add new num_arrows option to streamplot +* :ghpull:`29135`: Deprecate ListedColormap(..., N=...) parameter +* :ghpull:`29147`: Simplify synthetic event generation in interactive pan/zoom tests. +* :ghpull:`29150`: TST: Run macosx backends in a subprocess +* :ghpull:`29066`: Check pressed mouse buttons in pan/zoom drag handlers. +* :ghpull:`29087`: DOC: escape broken cross links +* :ghpull:`29127`: MNT: Refactor matplotlib.colors.from_levels_and_colors() +* :ghpull:`29125`: Make ListedColormap.monochrome a property +* :ghpull:`29074`: Add "standard" Axes wrapper getters/setters for Axis invertedness. +* :ghpull:`29078`: Document how to discourage API +* :ghpull:`29079`: DOC: Replaced colormap for colorblindness +* :ghpull:`29077`: DOC: Replaced green with blue for colorblindness + +Issues (257): + +* :ghissue:`23290`: [Bug]: Constrained Layout scaling of layouts with submerged spines +* :ghissue:`31622`: [Bug]: ``tight`` and ``constrained`` layouts honouring invisible parts of ``floating_axis`` +* :ghissue:`31624`: [MNT]: PolarTransform deprecation didn't warn +* :ghissue:`31590`: Should ``_make_axis_parameter_optional`` handle ``None``? +* :ghissue:`25446`: [Bug]: Nan values in scatter 3d plot show in black colour when alpha parameter is passed. +* :ghissue:`22546`: [Doc]: svg.fonttype: None in custom style sheet gives an error +* :ghissue:`24958`: [Doc]: Provide a working example for turning on specific axes labels when sharex or sharey are used with subplots +* :ghissue:`25818`: [Doc]: Heatmap border pixels leak outside grid +* :ghissue:`31574`: [Bug]: polar projection with ``labels`` on ``set_ticks`` gives UserWarning +* :ghissue:`14480`: Multicolor errorbars cannot have caps +* :ghissue:`31330`: [Bug]: Crash when removing colorbar axes in a constrained layout +* :ghissue:`14235`: Add \underline to mathtext? +* :ghissue:`31462`: [Bug]: Errorbar plot on log-scaled Axes sets incorrect automatic lower limits +* :ghissue:`30859`: [Bug]: ax.relim() ignores scatter artist +* :ghissue:`31523`: [Bug]: twinx() and twiny() crash with cryptic errors on 3D axes +* :ghissue:`26901`: [ENH]: Remove ``canvas.draw`` from ``widgets.Cursor.onmove`` +* :ghissue:`30831`: [Bug]: AttributeError: 'TimedAnimation' object has no attribute '_framedata' +* :ghissue:`31513`: [Bug]: Flaky test_contour.py::test_labels on minver CI +* :ghissue:`24716`: [TST]: Add classic style to all old image tests. +* :ghissue:`28488`: [ENH]: Provide a way to avoid subcommands on import. +* :ghissue:`30413`: [MNT]: c++11 narrowing error when building for 32 bit targets +* :ghissue:`31122`: [ENH]: Give control whether twinx() or twiny() overlays the main axis +* :ghissue:`4822`: Light font variants cannot be accessed by common name +* :ghissue:`21409`: [Bug]: twinx and twiny ignores previous set_position +* :ghissue:`31404`: [Bug]: Crash when removing contour set after removing contour labels +* :ghissue:`30365`: [Bug]: Type hints for xy and xycoords in annotate are too strict +* :ghissue:`13044`: Center of rotation for text with rotation_mode='anchor' +* :ghissue:`29253`: [Bug]: Numbers in words not italic +* :ghissue:`31220`: Should we use font metrics for line height instead of "lp"? +* :ghissue:`22172`: [Bug]: \genfrac has bad spacing with (high) custom ruler +* :ghissue:`18389`: Vertical positioning in mathtext fraction rendering could be improved +* :ghissue:`18086`: sub/superscripts should be moved further from the baseline following large delimiters +* :ghissue:`3135`: Please add support for ttc font files (PDF/PS output) +* :ghissue:`16566`: OTF feature support (alternate figure styles, etc.) +* :ghissue:`20842`: [MNT]: Please update freetype version +* :ghissue:`8765`: Indic Script labels not rendered correctly +* :ghissue:`2071`: matplotlib can't handle "newer" TrueType fonts +* :ghissue:`23082`: [Bug]: Font rendering bug for Devanagari text +* :ghissue:`29357`: [Bug]: Incorrect rendering of Abugida fonts on Matplotlib visualization +* :ghissue:`29806`: [Feature Request] Proper Arabic Language Support in Matplotlib Plots +* :ghissue:`5210`: Unexpected replacement of \right) with exclamation point in MathTextParser output +* :ghissue:`9681`: Determine if ``hinting_factor`` setting can be dropped +* :ghissue:`21797`: [Bug]: Math fonts (Type 3) incorrectly embedded in PDF? +* :ghissue:`31464`: [Doc]: finding the simple example +* :ghissue:`31454`: [Doc]: Amend AI policy by a concrete list of dos and don’ts +* :ghissue:`31337`: wording questions +* :ghissue:`31406`: [ENH]: [Bug]: secondary_xaxes().set_xlim/xbound should warn or raise that it is ineffective +* :ghissue:`31400`: [ENH]: Support partial figsize setting +* :ghissue:`26620`: [Doc]: Improve legend loc and bbox_to_anchor documentation +* :ghissue:`31369`: Dead link in colormap docs [Ware] +* :ghissue:`31344`: [Bug]: Adding contour labels affects the shape of filled contours +* :ghissue:`31286`: [MNT]: Scale ``val_in_range`` method +* :ghissue:`30651`: [MNT]: Add copyright information for google's "turbo" colormap? +* :ghissue:`28298`: [Bug]: set linestyle='dashed' raise error with quiver and legend +* :ghissue:`31302`: ``stairs`` with dashed linestyle and fill=True raises ValueError +* :ghissue:`27870`: [ENH]: out-of-tree Pyodide builds in CI for Matplotlib +* :ghissue:`31164`: [MNT]: Adopt Scikit Learn's autoclose bot +* :ghissue:`31320`: [DOC]: Using matplotlib.pyplot.pcolormesh with shading='flat' +* :ghissue:`31247`: [Bug]: Changing limits by setting ticks does not emit "x/ylim_changed" +* :ghissue:`18159`: Add zoom_factory to matplotlib - where to put? +* :ghissue:`31235`: [Doc]: bad rendering of matplotlib.pyplot.quiver docs +* :ghissue:`31126`: [Bug]: FigureCanvasTkAgg renders clipped/oversized when embedded in layout-managed container on Windows HiDPI +* :ghissue:`15313`: star (*) symbol in text box cuts off bottom of text when saved +* :ghissue:`31182`: [Bug]: ``ax.hist()`` fails on sequence of timedeltas due to comparison with ``np.inf`` +* :ghissue:`31256`: [ENH]: Extend semilogx, etc to 3D +* :ghissue:`209`: 3D scatter plots don't work in logscale +* :ghissue:`23306`: [ENH]: allow passing a function to ``CallbackRegistry.disconnect`` +* :ghissue:`28766`: [Bug]: Alignment of minus sign when using LaTeX +* :ghissue:`31093`: [ENH]: Modifier key to discretize rotations for 3D plots +* :ghissue:`31194`: [ENH]: add ``errorbar.capthick`` and ``errorbar.elinewidth`` to mplstyle +* :ghissue:`31221`: [Doc]: ticks/ticklabels_rotation example should mention rotation_mode="xtick"/"ytick" +* :ghissue:`20779`: [ENH]: move .matplotlib folder from %USERPROFILE% on Windows +* :ghissue:`31225`: [Bug]: set_edgecolor(None) cannot recover the default style after changing the edge color of wedges with hatches +* :ghissue:`26092`: [Bug]: alpha array-type not working with RGB image in imshow() +* :ghissue:`31009`: [Bug]: Large pixels may overlap when using imshow() +* :ghissue:`31127`: [Doc]: quiver 3d does not support "mid" as an alias for "middle", but quiver 2d does +* :ghissue:`30848`: [MNT]: Should we request contributors to declare usage of AI? +* :ghissue:`25914`: [Doc]: replace usages of ``.imread`` with PIL.Image.open +* :ghissue:`30934`: [ENH]: Implement gapcolor for patch edges +* :ghissue:`24499`: [Doc]: Transformation tutorial uses outdated description for polar transform +* :ghissue:`31149`: [ENH]: Improve compatibility with array-like objects implementing __array__ (e.g. MLX arrays) +* :ghissue:`31135`: [Bug]: Setting figure for polar axes breaks the polar coordinates +* :ghissue:`28793`: ``patheffects.SimpleLineShadow`` calling non-existent ``get_foreground`` method from GraphicsContextBase +* :ghissue:`30658`: [MNT]: First contributor workflow fails for first contributors +* :ghissue:`19299`: wide mathtext accents are mis-centered +* :ghissue:`31086`: [Bug]: Colorbar get_ticks() return the incorrect array +* :ghissue:`2488`: Off-axes scatter() points unnecessarily saved to PDF when coloured +* :ghissue:`29551`: [Bug]: 3D tick label position jitter when rotating the plot view +* :ghissue:`30957`: [MNT]: Clarify the difference between Artist.set and Artist.update +* :ghissue:`30996`: [Doc]: ``contour`` and ``contourf`` levels default not specified +* :ghissue:`31003`: [ENH]: Add types for ``fig_kw`` argument in ``subplots`` +* :ghissue:`30417`: [ENH]: Support using datetimes as ``positions`` argument to violin(...) +* :ghissue:`30575`: [Bug]: Regression in widget behavior +* :ghissue:`23763`: [Bug]: Inconsistent rendering between backends when rendering Mathtext horizontal rule +* :ghissue:`23860`: [Bug]: Font weight of label cannot be overwritten from rcParams when using mathtext +* :ghissue:`29475`: [Doc]: interactive rebase instructions outdated? +* :ghissue:`29863`: [ENH]: Should we hide _preprocess_data from the stack trace? +* :ghissue:`30836`: [Bug]: Markers can be integers, but numpy integers fail +* :ghissue:`22231`: [Bug]: Axes.grid(color) ignores alpha +* :ghissue:`14143`: imshow pixel boundaries wrong when zoomed in +* :ghissue:`1441`: Misalignment imshow vs. grid lines +* :ghissue:`30882`: [Bug]: Flaky tests with "Python 3.11 on ubuntu-22.04 (Minimum Versions)" +* :ghissue:`27590`: [Bug]: Qt5 backend icons should be white when macOS in dark mode +* :ghissue:`23531`: [Doc]: Documentation of rc parameters could be improved +* :ghissue:`30559`: [ENH]: Backend versioning +* :ghissue:`30917`: [Bug]: TimerAsyncio does not work with Python 3.14 +* :ghissue:`30709`: [Bug]: Mismatch in documented default behaviour of pcolormesh 'snap' +* :ghissue:`30463`: [Doc]: Two sources of a gallery figure for normal and high-DPI screen are different +* :ghissue:`28983`: [Doc]: outdated links for violin/boxplot +* :ghissue:`30857`: [Bug]: ValueError: The 'color' keyword argument must have one color per dataset +* :ghissue:`29332`: [ENH]: Typing: broaden acceptable floats +* :ghissue:`23633`: [MNT]: Deprecated / discourage less used Axes methods forwarding to Axis methods +* :ghissue:`21496`: [MNT]: MultiCursor should not take canvas as first parameter +* :ghissue:`30563`: [Bug]: 3D text does not respect rotation to make it parallel with a given zdir axis +* :ghissue:`27969`: [ENH]: Please add ``matplotlib.patches.RoundedRectangle`` +* :ghissue:`29319`: [Bug]: Legend with location set to ‘best’ overlaps with the title when the titles is moved down +* :ghissue:`28513`: [Bug]: Segfault when using ``close_event`` with macosx backend and tk +* :ghissue:`30840`: [MNT]: ``LocationEvent.modifiers`` missing in type stub +* :ghissue:`30770`: [Bug]: Bug / Inconsistency: Title Format Lost After Interactive Editing +* :ghissue:`30673`: [ENH]: Add custom hatch styling to grouped_bar +* :ghissue:`30804`: [Bug]: Stackplot does not pass ``facecolor(s)`` correctly to fill_between +* :ghissue:`30537`: Permanent solution for GioUnix warning +* :ghissue:`27224`: [Bug]: pickling and unpickling hidpi a qt figure that has been already shown doubles its physical size +* :ghissue:`26380`: [Bug]: DPI keeps doubling when creating a new MatPlotLib QtWidget in qt6 +* :ghissue:`20415`: figure.raise_window keyword produces inconsistent results +* :ghissue:`18985`: Why does setting imshow(animated=True) still show an image? +* :ghissue:`22831`: [Doc]: Arguments of FFMpegFileWriter not clear. +* :ghissue:`30796`: [Doc]: Information about deprecated colormaps missing from recent versions of the documentation +* :ghissue:`7059`: Decoupling hatch from edges +* :ghissue:`30744`: [Bug]: axis3d.Axis.get_tightbbox() is not including the offset_text +* :ghissue:`30767`: [ENH]: Add rcParams for the width of the legend's box edge +* :ghissue:`30472`: [Bug]: layout=compressed conflict with suptitle +* :ghissue:`23998`: Labels for PatchCollection do not show +* :ghissue:`28889`: [Doc]: Reintroduce glossary for matplotlib terms and concepts +* :ghissue:`22402`: [Doc]: Quiver docstring incorrectly claims that only ``UVC`` can be set +* :ghissue:`19338`: Allow option to display absolute values for pie chart +* :ghissue:`30664`: [MNT]: Declare table() to be not further developed +* :ghissue:`30764`: [Bug]: Hexbin with bins='log' doesn't handle zeros as described +* :ghissue:`30439`: [Doc]: Link AI policy on contributing page +* :ghissue:`30740`: [ENH]: Support caption for code block in sphinx plot directive +* :ghissue:`30695`: [Bug]: bbox_inches='tight' works differently when ax.plot() have markers +* :ghissue:`30257`: [MNT] [TYPING]: Use of Literal +* :ghissue:`20724`: ToolHandles/ToolLineHandles could set the mouse cursor when hovered over or active +* :ghissue:`20554`: Remove discussion of jupyter backends from image tutorial +* :ghissue:`28827`: [Bug]: FontProperties objects are not deleted when using fig.savefig +* :ghissue:`30644`: [Doc]: Stable docs reporting as unstable +* :ghissue:`30613`: [Bug]: violin's default alpha no longer persists +* :ghissue:`22197`: [Bug]: TwoSlopeNorm behaves like CenteredNorm +* :ghissue:`30522`: [MNT]: PR Greeting GHA not working +* :ghissue:`30574`: [Bug]: Unicode symbols encoded with ``\u....`` with mathtext raise ParseFatalException +* :ghissue:`27190`: [Doc]: clarify when and how to use boilerplate.py +* :ghissue:`26739`: Write a separate doc-string for Line3DCollection +* :ghissue:`19956`: Native support for showing OOP-created figures +* :ghissue:`28412`: [ENH]: Zoom in/out on rolling the mouse wheel +* :ghissue:`30525`: [Bug]: Pipeline fails with "GioUnix was imported without specifying a version first" +* :ghissue:`30436`: [Doc]: new contributor guidance on draft PRs +* :ghissue:`30364`: [MNT]/[DOC]: Look into Axes3D.set_aspect ``anchor`` and ``adjustable`` arguments +* :ghissue:`30474`: [Bug]: Typo in method name: contains_branch_separately +* :ghissue:`30418`: [Bug]: error using ``add_collection3d`` of ``Line3DCollection`` with ``autolims=True`` and lines containing different numbers of points +* :ghissue:`30263`: [ENH]: Allow ignoring x-extent (but not y-extent) of xticklabels when computing axes extents (e.g. for geometry manager) +* :ghissue:`30296`: [MNT]: Deprecate the axes parameter to RadialLocator +* :ghissue:`29774`: [Bug]: triage_tests.py is brittle against failures in test modules that have only check_figures_equal test +* :ghissue:`29349`: [MNT]: Remove axis parameter from scales +* :ghissue:`1963`: Singular keyword arguments in contour don't raise exceptions +* :ghissue:`30449`: [Bug]: Config directory location finder doesn't account for the home directory being undetermined. +* :ghissue:`30438`: [Bug]: missing stubs for ``plt.cm`` (a.k.a. ``matplotlib.pyplot.cm``) +* :ghissue:`30298`: [Bug]: Legend kwarg ``labelcolor='linecolor'`` not working properly when ``facecolor`` is ``'None'`` +* :ghissue:`30437`: [Doc]: Clarification of relative font sizes +* :ghissue:`30400`: [Bug]: Megabyte-level memory leak when using imshow() in a loop +* :ghissue:`29957`: [ENH]: add_collection(..., autolim=True) should update view limits as well +* :ghissue:`22720`: [MNT]: Generalize widget mouse testing +* :ghissue:`28809`: [ENH]: Support avif as output format +* :ghissue:`30331`: [ENH]: inset_axes has borderpadding, but not x/y individually. +* :ghissue:`29300`: [Bug]: Background of rotated png is rendered black +* :ghissue:`30323`: [MNT]: validate linewidth +* :ghissue:`25572`: [Bug]: Artist.remove() isn't fully removing it from figure +* :ghissue:`30325`: [Bug]: fig.savefig throws error after radiobutton axes is removed +* :ghissue:`15529`: Chinese font can``t change the weight +* :ghissue:`30164`: [Bug]: Removing spines in polar plot causes distortion of the plot +* :ghissue:`27232`: BUG: .notdef glyph has to be present in fonts in fontlist +* :ghissue:`14239`: rotated text does not align +* :ghissue:`23021`: [Bug]: Text rotation leads to characters being misplaced within their bounding boxes. Attempted solution provided. +* :ghissue:`30160`: [MNT]: pyplot type hints +* :ghissue:`13919`: Impossible to configure minor/major grid line style independently in rcParams +* :ghissue:`25800`: [MNT]: Remove the userdemo section in examples +* :ghissue:`24313`: [ENH]: API discussion for grouped bar charts +* :ghissue:`29722`: [MNT]: Upcoming version of ``pyparsing`` will start emitting ``DeprecationWarnings`` for legacy pre-PEP8 method and argument names +* :ghissue:`30026`: [Doc]: add histogram as colorbar example +* :ghissue:`127`: When text.usetex=True with pdf backend, full subset of latex fonts is embedded into pdf file +* :ghissue:`10034`: Hatching is rendered differently by agg, pdf and svg backends. +* :ghissue:`19832`: Positioning floating_axes.FloatingSubplot +* :ghissue:`29791`: [Bug]: Saving as an SVG and PDF produce different outputs with Latex characters, with wrong character sizing +* :ghissue:`28675`: [Bug]: ``multialignment='right'`` in ``ax.text()`` with ``path_effects`` breaks when using LaTeX package ``\usepackage[T1]{fontenc}`` +* :ghissue:`27654`: [MNT]: Use fix-cm rather than type1cm for LaTeX +* :ghissue:`30086`: Add petroff6 and petroff8 color cycles to named color sequences example +* :ghissue:`30060`: Add the 6 color and 8 color sequence for the Petroff color cycles +* :ghissue:`28750`: Followup documentation for petroff color sequence +* :ghissue:`18931`: 3D collections do not proper handle ``edgecolor='face'`` +* :ghissue:`2831`: Bug when saving to vector format (pdf, svg, eps) +* :ghissue:`30046`: [Doc]: Documentation of the stable version still prompts that it is an unstable development version +* :ghissue:`29844`: [MNT]: CI: pygobject fails to install during ubuntu-22.04 GitHub Actions jobs +* :ghissue:`30021`: [Bug]: Setting font.sans-serif is impossible by the intended way using matplotlib.rc because it contains a hyphen. +* :ghissue:`30007`: Axes.pie([0, 0]) crashes with “cannot convert float NaN to integer” when all slice sizes are zero +* :ghissue:`29334`: [Bug]: Type annotation for ``add_subplots`` has incorrect return type for ``projection="3d"`` +* :ghissue:`29681`: [ENH]: Add parameter 'error_linestyle' to plt.errorbar() +* :ghissue:`29960`: [Bug]: FuncAnimation function not typed properly +* :ghissue:`29860`: ``ax.pie()`` raises ``ValueError`` when input contains ``NaN`` +* :ghissue:`11059`: figure.clf() and subplots_adjust +* :ghissue:`29906`: [Doc]: Autoscaling Axes or Autoscaling Axis? +* :ghissue:`29921`: boilerplate.py seems to remove parameters +* :ghissue:`29938`: [ENH]: plt.colorbar add a colorbar which has the same height/width of original image +* :ghissue:`29891`: [Bug]: image alpha re-applied each draw? +* :ghissue:`29883`: [Bug]: Missing backcompat for backends not supporting hatchcolors in draw_quad_mesh +* :ghissue:`27588`: [ENH]: Add way to automatically fix flake8 errors +* :ghissue:`1369`: add rc param for centimeter support +* :ghissue:`29845`: [MNT]: CI: cleanup: remove stale/outdated version range restrictions +* :ghissue:`29749`: [Bug]: Unit tests: Ubuntu 22.04 lacks dependencies required for recent PyGObject versions +* :ghissue:`29615`: [Bug]: pcolormesh's default x/y range might break ``set_scale('log')`` +* :ghissue:`29528`: [Bug]: set_rticks makes polar autoscale move the origin away from zero +* :ghissue:`29799`: [ENH]: set default color cycle to named color sequence +* :ghissue:`29694`: [Bug]: LogLocator sometimes draws fewer ticks than it can +* :ghissue:`29746`: [Doc]: Add uv and pixi install instructions +* :ghissue:`29647`: [ENH]: Allow list of padding values for bar_label +* :ghissue:`27669`: [Doc]: documentation of how to properly rasterize output of contourf +* :ghissue:`29757`: [Doc]: duplicate tags +* :ghissue:`29753`: [Doc]: color and colormap tags +* :ghissue:`29720`: [Bug]: Inset Axes Failing for Geographic Plot +* :ghissue:`29712`: [Doc]: Stable version of documentation has unstable banner +* :ghissue:`27196`: [Doc]: List supported hatches and link to/embed hatch reference on hatches API page +* :ghissue:`29562`: [MNT]: Remove rcParams deprecation machinery +* :ghissue:`29042`: [Bug]: colors.LinearSegmentedColormap.from_list fails when using a ("", alpha) tuple +* :ghissue:`28951`: [ENH]: Better positioning of rotated tick labels +* :ghissue:`29474`: [ENH]: Show parameter names in error message for mismatched array sizes in bar() +* :ghissue:`27298`: [ENH]: Add color argument to violinplot constructor +* :ghissue:`22861`: [Bug]: 3D scatter plot flips alpha order depending on depth relative to camera +* :ghissue:`29532`: [Bug]: ScalarFormatter can't be forced to use an offset of 1 +* :ghissue:`16659`: Speeding up Axes3D.plot_surface 4-8x +* :ghissue:`29524`: [Doc]: Unclear how to compile ``c_internals`` in code space +* :ghissue:`29489`: [Bug]: Systematic test failures with ubuntu-22.04-arm pipeline +* :ghissue:`28915`: [Doc]: Preferred way of specifying colormaps via ``cmap`` +* :ghissue:`29305`: [Doc]: Dead link in dev workflow docs +* :ghissue:`28763`: [MNT]: ListedColormap inconsistencies +* :ghissue:`29428`: [Doc]: Multipage PDF: unclear which backend supports and which does not support attach_note() +* :ghissue:`29387`: [MNT]: Fix 3.10 release notes and merge up +* :ghissue:`27321`: [Bug]: The method for checking whether a color has an alpha value is outdated +* :ghissue:`29284`: [Bug]: ``get_ticklabels``/``set_ticklabels`` gives incorrect values in log plot +* :ghissue:`26074`: [ENH]: Different edgecolor and hatch color in bar plot +* :ghissue:`29313`: [DOC]: possible typos +* :ghissue:`27763`: [Bug]: colorbar doesn't register inset_axis as cax +* :ghissue:`23770`: [Bug]: crash due to backend issue in ipython session started explicitly with InteractiveShell +* :ghissue:`19017`: Formalize discouraged API (= softer deprecations) +* :ghissue:`22521`: [Bug]: X-Axis date label not rotated +* :ghissue:`29181`: [Doc]: locally testing changes +* :ghissue:`17740`: Multiple Arrows on Streamplots +* :ghissue:`19101`: support for ticks crossing axes in axisartist +* :ghissue:`24050`: No error message in matplotlib.axes.Axes.legend() if there are more labels than handles +* :ghissue:`7305`: RuntimeError In FT2Font with NISC18030.ttf Previous GitHub statistics diff --git a/doc/release/next_whats_new/pie_wedge_labels.rst b/doc/release/next_whats_new/pie_wedge_labels.rst new file mode 100644 index 000000000000..9c72742e005e --- /dev/null +++ b/doc/release/next_whats_new/pie_wedge_labels.rst @@ -0,0 +1,26 @@ +New *wedge_labels* parameter for pie +------------------------------------ + +`~.Axes.pie` now accepts a *wedge_labels* parameter as a shortcut to the +`~.Axes.pie_label` method. This may be used for simple annotation of the wedges +of the pie chart. It can take + +* a list of strings, similar to the existing *labels* parameter +* a format string similar to the existing *autopct* parameter, except that it + uses the `str.format` method and it can handle absolute values as well as + fractions/percentages + +*wedge_labels* has an accompanying *wedge_label_distance* parameter, to control +the distance of the labels from the center of the pie. + + +.. plot:: + :include-source: true + :alt: Two pie charts. The chart on the left has labels 'foo' and 'bar' outside the wedges. The chart on the right has labels '1' and '2' inside the wedges. + + import matplotlib.pyplot as plt + + fig, (ax1, ax2) = plt.subplots(ncols=2, layout='constrained') + + ax1.pie([1, 2], wedge_labels=['foo', 'bar'], wedge_label_distance=1.1) + ax2.pie([1, 2], wedge_labels='{absval:d}', wedge_label_distance=0.6) diff --git a/doc/release/next_whats_new/plot_skip_execution.rst b/doc/release/next_whats_new/plot_skip_execution.rst new file mode 100644 index 000000000000..75d95bbada17 --- /dev/null +++ b/doc/release/next_whats_new/plot_skip_execution.rst @@ -0,0 +1,11 @@ +New config option for ``matplotlib.sphinxext.plot_directive``: ``plot_skip_execution`` +-------------------------------------------------------------------------------------- + +This configuration option allows users to temporarily skip the execution of all +plot directives, not running the code or generating the plots. It is intended to +be used during development to speed up building documentation that contains many +plot directives. + +It can be temporarily enabled from the command line by passing ``-D +plot_skip_execution=1`` to ``sphinx-build``, e.g.,: ``make html O="-D +plot_skip_execution=1"``. diff --git a/doc/release/next_whats_new/polar_get_rlim_thetalim.rst b/doc/release/next_whats_new/polar_get_rlim_thetalim.rst new file mode 100644 index 000000000000..57586d2a32ce --- /dev/null +++ b/doc/release/next_whats_new/polar_get_rlim_thetalim.rst @@ -0,0 +1,15 @@ +``PolarAxes.get_rlim()`` and ``get_thetalim()`` added +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +:class:`~matplotlib.projections.polar.PolarAxes` now provides +`~matplotlib.projections.polar.PolarAxes.get_rlim` and +`~matplotlib.projections.polar.PolarAxes.get_thetalim` to complement the +existing `~matplotlib.projections.polar.PolarAxes.set_rlim` and +`~matplotlib.projections.polar.PolarAxes.set_thetalim`. Previously, one +had to use `.Axes.get_ylim`, `.Axes.get_xlim` as a workaround. + +:: + + ax = plt.subplot(projection="polar") + ax.set_rlim(1, 5) + rmin, rmax = ax.get_rlim() # was: AttributeError diff --git a/doc/release/prev_whats_new/github_stats_3.10.9.rst b/doc/release/prev_whats_new/github_stats_3.10.9.rst new file mode 100644 index 000000000000..73d2785531b4 --- /dev/null +++ b/doc/release/prev_whats_new/github_stats_3.10.9.rst @@ -0,0 +1,103 @@ +.. _github-stats_3-10-9: + +GitHub statistics for 3.10.9 (Apr 23, 2026) +=========================================== + +GitHub statistics for 2024/12/14 (tag: v3.10.0) - 2026/04/23 + +These lists are automatically generated, and may be incomplete or contain duplicates. + +We closed 10 issues and merged 34 pull requests. +The full list can be seen `on GitHub `__ + +The following 37 authors contributed 519 commits. + +* Aasma Gupta +* Aman Srivastava +* Antony Lee +* beelauuu +* Ben Root +* Christine P. Chai +* David Stansby +* dependabot[bot] +* Elliott Sales de Andrade +* G.D. McBain +* Greg Lucas +* hannah +* hu-xiaonan +* Ian Thomas +* Inês Cachola +* Jody Klymak +* Jouni K. Seppänen +* Khushi_29 +* Kyle Sunden +* Lumberbot (aka Jack) +* m-sahare +* N R Navaneet +* Nathan G. Wiseman +* Oscar Gustafsson +* Praful Gulani +* Qian Zhang +* Raphael Erik Hviding +* Raphael Quast +* Roman +* Ruth Comer +* saikarna913 +* Scott Shambaugh +* Steve Berardi +* Thomas A Caswell +* Tim Hoffmann +* Trygve Magnus Ræder +* Vikash Kumar + +GitHub issues and pull requests: + +Pull Requests (34): + +* :ghpull:`31556`: FIX: Inverted PyErr_Occurred check in enum type caster (_enums.h) +* :ghpull:`31078`: Backport PR #31075 on branch v3.10.x (Fix remove method for figure title and xy-labels) +* :ghpull:`31280`: Backport PR #31278 on branch v3.10.x (Fix ``clabel`` manual argument not accepting unit-typed coordinates) +* :ghpull:`31520`: Backport PR #31020 on branch v3.10.x (DOC: Fix doc builds with Sphinx 9) +* :ghpull:`31511`: Backport PR #31504 on branch v3.10.x (Re-order variants to prioritize narrower types) +* :ghpull:`31504`: Re-order variants to prioritize narrower types +* :ghpull:`31445`: Backport PR #31437: mathtext: Fix type inconsistency with fontmaps +* :ghpull:`31437`: mathtext: Fix type inconsistency with fontmaps +* :ghpull:`31411`: Backport PR #31323 on branch v3.10.x (FIX: Prevent crash when removing a subfigure containing subplots) +* :ghpull:`31421`: Backport PR #31420 on branch v3.10.x (Fix outdated Savannah URL for freetype download) +* :ghpull:`31420`: Fix outdated Savannah URL for freetype download +* :ghpull:`31418`: Backport PR #31401: BLD: Temporarily pin setuptools-scm<10 +* :ghpull:`31323`: FIX: Prevent crash when removing a subfigure containing subplots +* :ghpull:`31401`: BLD: Temporarily pin setuptools-scm<10 +* :ghpull:`31278`: Fix ``clabel`` manual argument not accepting unit-typed coordinates +* :ghpull:`31154`: Backport PR #31153 on branch v3.10.x (TST: Use correct method of clearing mock objects) +* :ghpull:`31153`: TST: Use correct method of clearing mock objects +* :ghpull:`31075`: Fix remove method for figure title and xy-labels +* :ghpull:`31036`: Backport PR #31035 on branch v3.10.x (DOCS: Fix typo in time array step size comment) +* :ghpull:`30986`: Backport PR #30985 on branch v3.10.x (MNT: do not assign a numpy array shape) +* :ghpull:`30985`: MNT: do not assign a numpy array shape +* :ghpull:`30971`: Backport PR #30969 on branch v3.10.x (DOC: Simplify barh() example) +* :ghpull:`30965`: Backport PR #30952 on branch v3.10.x (DOC: Tutorial on API shortcuts) +* :ghpull:`30964`: Backport PR #30960 on branch v3.10.x (SVG backend - handle font weight as integer) +* :ghpull:`30960`: SVG backend - handle font weight as integer +* :ghpull:`30924`: Backport PR #30910 on branch v3.10.x (DOC: Improve writer parameter docs of Animation.save()) +* :ghpull:`30870`: Backport PR #30869 on branch v3.10.x (FIX: Accept array for zdir) +* :ghpull:`30869`: FIX: Accept array for zdir +* :ghpull:`30860`: Backport PR #30858 on branch v3.10.x (DOC: reinstate "codex" search term) +* :ghpull:`30818`: Backport PR #30817 on branch v3.10.x (Update sphinx-gallery header patch) +* :ghpull:`30801`: Backport PR #30763 on branch v3.10.x (DOC: Add example how to align tick labels) +* :ghpull:`30791`: Backport PR #30788 on branch v3.10.8-doc (Fix typo in key-mapping for "f11") +* :ghpull:`30790`: Backport PR #30788 on branch v3.10.x (Fix typo in key-mapping for "f11") +* :ghpull:`30788`: Fix typo in key-mapping for "f11" + +Issues (10): + +* :ghissue:`31495`: Unavoidable warnings with pybind11 main branch +* :ghissue:`31433`: [MNT]: Mypy error +* :ghissue:`31340`: [Bug]: outdated savannah URL in subprojects/freetype-2.6.1.wrap +* :ghissue:`31319`: [Bug]: Crash when removing a subfigure with a subplot in a figure +* :ghissue:`27525`: [Bug]: clabel manual argument does not accept units +* :ghissue:`31112`: [TST] Upcoming dependency test failures +* :ghissue:`31073`: [Bug]: Crash when Removing Suptitle in a Figure with Constrained Layout +* :ghissue:`30981`: [TST] Upcoming dependency test failures +* :ghissue:`30868`: [Bug]: Axe3D text() method does not allow zdir=numpy.array(...) +* :ghissue:`21566`: [ENH]: set_horizontalalignment("right") on Y axis labels when yaxis.ticks_right() is used. diff --git a/doc/release/release_notes.rst b/doc/release/release_notes.rst index e2cd258ee3a7..d652f5dbcf0f 100644 --- a/doc/release/release_notes.rst +++ b/doc/release/release_notes.rst @@ -13,6 +13,13 @@ Release notes .. include:: release_notes_next.rst +Version 3.11 +^^^^^^^^^^^^ +.. toctree:: + :maxdepth: 1 + + github_stats.rst + Version 3.10 ^^^^^^^^^^^^ .. toctree:: @@ -23,7 +30,7 @@ Version 3.10 ../api/prev_api_changes/api_changes_3.10.7.rst ../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.9.rst prev_whats_new/github_stats_3.10.8.rst prev_whats_new/github_stats_3.10.7.rst prev_whats_new/github_stats_3.10.6.rst diff --git a/galleries/examples/misc/hyperlinks_sgskip.py b/galleries/examples/misc/hyperlinks_sgskip.py index 26421c941573..ea2870aeae3d 100644 --- a/galleries/examples/misc/hyperlinks_sgskip.py +++ b/galleries/examples/misc/hyperlinks_sgskip.py @@ -20,6 +20,11 @@ s.set_urls(['https://www.bbc.com/news', 'https://www.google.com/', None]) fig.savefig('scatter.svg') +# %% +# .. raw:: html +# +# + # %% fig = plt.figure() @@ -35,3 +40,8 @@ im.set_url('https://www.google.com/') fig.savefig('image.svg') + +# %% +# .. raw:: html +# +# diff --git a/galleries/examples/misc/svg_filter_pie.py b/galleries/examples/misc/svg_filter_pie.py index f8ccc5bcb22b..d438fe77b8a6 100644 --- a/galleries/examples/misc/svg_filter_pie.py +++ b/galleries/examples/misc/svg_filter_pie.py @@ -28,11 +28,11 @@ # We want to draw the shadow for each pie, but we will not use "shadow" # option as it doesn't save the references to the shadow patches. -pie = ax.pie(fracs, explode=explode, labels=labels, autopct='%1.1f%%') +pie = ax.pie(fracs, explode=explode, wedge_labels=labels, wedge_label_distance=1.1) -for w in pie.wedges: +for w, label in zip(pie.wedges, labels): # set the id with the label. - w.set_gid(w.get_label()) + w.set_gid(label) # we don't want to draw the edge of the pie w.set_edgecolor("none") diff --git a/galleries/examples/pie_and_polar_charts/bar_of_pie.py b/galleries/examples/pie_and_polar_charts/bar_of_pie.py index 7c703976db2e..6e58bba5209d 100644 --- a/galleries/examples/pie_and_polar_charts/bar_of_pie.py +++ b/galleries/examples/pie_and_polar_charts/bar_of_pie.py @@ -25,8 +25,11 @@ explode = [0.1, 0, 0] # rotate so that first wedge is split by the x-axis angle = -180 * overall_ratios[0] -pie = ax1.pie(overall_ratios, autopct='%1.1f%%', startangle=angle, - labels=labels, explode=explode) +pie = ax1.pie(overall_ratios, startangle=angle, explode=explode) + +# label the wedges with our label strings and the ratios as percentages +ax1.pie_label(pie, labels, distance=1.1) +ax1.pie_label(pie, '{frac:.1%}', distance=0.6) # bar chart parameters age_ratios = [.33, .54, .07, .06] diff --git a/galleries/examples/pie_and_polar_charts/pie_features.py b/galleries/examples/pie_and_polar_charts/pie_features.py index 8510c09f23a5..80b8ade230b2 100644 --- a/galleries/examples/pie_and_polar_charts/pie_features.py +++ b/galleries/examples/pie_and_polar_charts/pie_features.py @@ -15,15 +15,15 @@ # ------------ # # Plot a pie chart of animals and label the slices. To add -# labels, pass a list of labels to the *labels* parameter +# labels, pass a list of labels to the *wedge_labels* parameter. import matplotlib.pyplot as plt labels = 'Frogs', 'Hogs', 'Dogs', 'Logs' -sizes = [15, 30, 45, 10] +sizes = [12, 24, 36, 8] fig, ax = plt.subplots() -ax.pie(sizes, labels=labels) +ax.pie(sizes, wedge_labels=labels) # %% # Each slice of the pie chart is a `.patches.Wedge` object; therefore in @@ -31,16 +31,44 @@ # the *wedgeprops* argument, as demonstrated in # :doc:`/gallery/pie_and_polar_charts/nested_pie`. # +# Set label positions +# ------------------- +# If you want the labels outside the pie, set a *wedge_label_distance* greater than 1. +# This is the distance from the center of the pie as a fraction of its radius. + +fig, ax = plt.subplots() +ax.pie(sizes, wedge_labels=labels, wedge_label_distance=1.1) + +# %% +# # Auto-label slices # ----------------- # -# Pass a function or format string to *autopct* to label slices. +# Pass a format string to *wedge_labels* to label slices with their values... + +fig, ax = plt.subplots() +ax.pie(sizes, wedge_labels='{absval:.1f}') + +# %% +# +# ...or with their percentages... + +fig, ax = plt.subplots() +ax.pie(sizes, wedge_labels='{frac:.1%}') + +# %% +# +# ...or both. fig, ax = plt.subplots() -ax.pie(sizes, labels=labels, autopct='%1.1f%%') +ax.pie(sizes, wedge_labels='{absval:d}\n{frac:.1%}') + +# %% +# +# For more control over labels, or to add multiple sets, see +# :doc:`/gallery/pie_and_polar_charts/pie_label`. # %% -# By default, the label values are obtained from the percent size of the slice. # # Color slices # ------------ @@ -48,8 +76,7 @@ # Pass a list of colors to *colors* to set the color of each slice. fig, ax = plt.subplots() -ax.pie(sizes, labels=labels, - colors=['olivedrab', 'rosybrown', 'gray', 'saddlebrown']) +ax.pie(sizes, colors=['olivedrab', 'rosybrown', 'gray', 'saddlebrown']) # %% # Hatch slices @@ -58,22 +85,9 @@ # Pass a list of hatch patterns to *hatch* to set the pattern of each slice. fig, ax = plt.subplots() -ax.pie(sizes, labels=labels, hatch=['**O', 'oO', 'O.O', '.||.']) - -# %% -# Swap label and autopct text positions -# ------------------------------------- -# Use the *labeldistance* and *pctdistance* parameters to position the *labels* -# and *autopct* text respectively. - -fig, ax = plt.subplots() -ax.pie(sizes, labels=labels, autopct='%1.1f%%', - pctdistance=1.25, labeldistance=.6) +ax.pie(sizes, hatch=['**O', 'oO', 'O.O', '.||.']) # %% -# *labeldistance* and *pctdistance* are ratios of the radius; therefore they -# vary between ``0`` for the center of the pie and ``1`` for the edge of the -# pie, and can be set to greater than ``1`` to place text outside the pie. # # Explode, shade, and rotate slices # --------------------------------- @@ -86,11 +100,10 @@ # # This example orders the slices, separates (explodes) them, and rotates them. -explode = (0, 0.1, 0, 0) # only "explode" the 2nd slice (i.e. 'Hogs') +explode = (0, 0.2, 0, 0) # only "explode" the 2nd slice (i.e. 'Hogs') fig, ax = plt.subplots() -ax.pie(sizes, explode=explode, labels=labels, autopct='%1.1f%%', - shadow=True, startangle=90) +ax.pie(sizes, explode=explode, wedge_labels='{frac:.1%}', shadow=True, startangle=90) plt.show() # %% @@ -107,8 +120,7 @@ fig, ax = plt.subplots() -ax.pie(sizes, labels=labels, autopct='%.0f%%', - textprops={'size': 'small'}, radius=0.5) +ax.pie(sizes, wedge_labels='{frac:.1%}', textprops={'size': 'small'}, radius=0.5) plt.show() # %% @@ -119,8 +131,8 @@ # the `.Shadow` patch. This can be used to modify the default shadow. fig, ax = plt.subplots() -ax.pie(sizes, explode=explode, labels=labels, autopct='%1.1f%%', - shadow={'ox': -0.04, 'edgecolor': 'none', 'shade': 0.9}, startangle=90) +ax.pie(sizes, explode=explode, shadow={'ox': -0.04, 'edgecolor': 'none', 'shade': 0.9}, + startangle=90) plt.show() # %% diff --git a/galleries/examples/pyplots/pyplot_simple.py b/galleries/examples/pyplots/pyplot_simple.py index 48a862c7fee3..8da1e346c296 100644 --- a/galleries/examples/pyplots/pyplot_simple.py +++ b/galleries/examples/pyplots/pyplot_simple.py @@ -1,20 +1,30 @@ """ -=========== -Simple plot -=========== +========== +Basic plot +========== -A simple plot where a list of numbers are plotted against their index, -resulting in a straight line. Use a format string (here, 'o-r') to set the -markers (circles), linestyle (solid line) and color (red). +A basic plot using the :ref:`pyplot_interface`. + +- `~.pyplot.plot` plots the data y versus x as lines and/or markers. +- `~.pyplot.title`, `~.pyplot.xlabel` and `~.pyplot.ylabel` set the title, + x-axis label and y-axis label. +- `~.pyplot.show` displays the plot. .. redirect-from:: /gallery/pyplots/fig_axes_labels_simple .. redirect-from:: /gallery/pyplots/pyplot_formatstr +.. redirect-from:: /gallery/pyplots/pyplot_text """ import matplotlib.pyplot as plt +import numpy as np + +x = np.arange(0.0, 2.0, 0.01) +y = np.sin(2 * np.pi * x) -plt.plot([1, 2, 3, 4], 'o-r') -plt.ylabel('some numbers') +plt.plot(x, y) +plt.title("A basic plot using pyplot") +plt.xlabel('Time [s]') +plt.ylabel('Voltage [mV]') plt.show() # %% @@ -25,5 +35,7 @@ # in this example: # # - `matplotlib.pyplot.plot` +# - `matplotlib.pyplot.title` +# - `matplotlib.pyplot.ylabel` # - `matplotlib.pyplot.ylabel` # - `matplotlib.pyplot.show` diff --git a/galleries/examples/pyplots/pyplot_text.py b/galleries/examples/pyplots/pyplot_text.py deleted file mode 100644 index 72f977c2f985..000000000000 --- a/galleries/examples/pyplots/pyplot_text.py +++ /dev/null @@ -1,41 +0,0 @@ -""" -============================== -Text and mathtext using pyplot -============================== - -Set the special text objects `~.pyplot.title`, `~.pyplot.xlabel`, and -`~.pyplot.ylabel` through the dedicated pyplot functions. Additional text -objects can be placed in the Axes using `~.pyplot.text`. - -You can use TeX-like mathematical typesetting in all texts; see also -:ref:`mathtext`. - -.. redirect-from:: /gallery/pyplots/pyplot_mathtext -""" - -import matplotlib.pyplot as plt -import numpy as np - -t = np.arange(0.0, 2.0, 0.01) -s = np.sin(2*np.pi*t) - -plt.plot(t, s) -plt.text(0, -1, r'Hello, world!', fontsize=15) -plt.title(r'$\mathcal{A}\sin(\omega t)$', fontsize=20) -plt.xlabel('Time [s]') -plt.ylabel('Voltage [mV]') -plt.show() - -# %% -# -# .. admonition:: References -# -# The use of the following functions, methods, classes and modules is shown -# in this example: -# -# - `matplotlib.pyplot.hist` -# - `matplotlib.pyplot.xlabel` -# - `matplotlib.pyplot.ylabel` -# - `matplotlib.pyplot.text` -# - `matplotlib.pyplot.grid` -# - `matplotlib.pyplot.show` diff --git a/galleries/examples/pyplots/pyplot_three.py b/galleries/examples/pyplots/pyplot_three.py deleted file mode 100644 index b14998cca4c9..000000000000 --- a/galleries/examples/pyplots/pyplot_three.py +++ /dev/null @@ -1,26 +0,0 @@ -""" -=========================== -Multiple lines using pyplot -=========================== - -Plot three datasets with a single call to `~matplotlib.pyplot.plot`. -""" - -import matplotlib.pyplot as plt -import numpy as np - -# evenly sampled time at 200ms intervals -t = np.arange(0., 5., 0.2) - -# red dashes, blue squares and green triangles -plt.plot(t, t, 'r--', t, t**2, 'bs', t, t**3, 'g^') -plt.show() - -# %% -# -# .. admonition:: References -# -# The use of the following functions, methods, classes and modules is shown -# in this example: -# -# - `matplotlib.axes.Axes.plot` / `matplotlib.pyplot.plot` diff --git a/galleries/examples/pyplots/pyplot_two_subplots.py b/galleries/examples/pyplots/pyplot_two_subplots.py index 2eb0237d5521..b532c7b1534d 100644 --- a/galleries/examples/pyplots/pyplot_two_subplots.py +++ b/galleries/examples/pyplots/pyplot_two_subplots.py @@ -3,7 +3,18 @@ Two subplots using pyplot ========================= -Create a figure with two subplots using `.pyplot.subplot`. +A typical pyplot usage pattern is to create subplots incrementally through +`~.pyplot.subplot`. + +The three-digit number passed to `~.pyplot.subplot` specifies the position of +the subplot in the grid of subplots. ``211`` means "in a grid of 2 rows and 1 column, +create this subplot in the 1st position". ``212`` likewise means "in a grid of 2 +rows and 1 column, create this subplot in the 2nd position". + +After calling ``subplot()`` all following pyplot commands will modify that subplot +until a new subplot is created. + +.. redirect-from:: /gallery/pyplots/pyplot_three """ import matplotlib.pyplot as plt @@ -21,9 +32,11 @@ def f(t): plt.subplot(211) plt.plot(t1, f(t1), color='tab:blue', marker='o') plt.plot(t2, f(t2), color='black') +plt.title("Subplot 1") plt.subplot(212) plt.plot(t2, np.cos(2*np.pi*t2), color='tab:orange', linestyle='--') +plt.title("Subplot 2") plt.show() # %% diff --git a/galleries/examples/subplots_axes_and_figures/share_axis_lims_views.py b/galleries/examples/subplots_axes_and_figures/share_axis_lims_views.py deleted file mode 100644 index e0aa04d13def..000000000000 --- a/galleries/examples/subplots_axes_and_figures/share_axis_lims_views.py +++ /dev/null @@ -1,32 +0,0 @@ -""" -=========================== -Share axis limits and views -=========================== - -It's common to make two or more plots which share an axis, e.g., two subplots -with time as a common axis. When you pan and zoom around on one, you want the -other to move around with you. To facilitate this, matplotlib Axes support a -``sharex`` and ``sharey`` attribute. When you create a `~.pyplot.subplot` or -`~.pyplot.axes`, you can pass in a keyword indicating what Axes you want to -share with. -""" - -import matplotlib.pyplot as plt -import numpy as np - -t = np.arange(0, 10, 0.01) - -ax1 = plt.subplot(211) -ax1.plot(t, np.sin(2*np.pi*t)) - -ax2 = plt.subplot(212, sharex=ax1) -ax2.plot(t, np.sin(4*np.pi*t)) - -plt.show() - -# %% -# .. tags:: -# -# component: axis -# plot-type: line -# level: beginner diff --git a/galleries/examples/subplots_axes_and_figures/shared_axis_demo.py b/galleries/examples/subplots_axes_and_figures/shared_axis_demo.py index 848db115456a..e5926e8f7ff4 100644 --- a/galleries/examples/subplots_axes_and_figures/shared_axis_demo.py +++ b/galleries/examples/subplots_axes_and_figures/shared_axis_demo.py @@ -3,46 +3,38 @@ Shared axis =========== -You can share the x- or y-axis limits for one axis with another by -passing an `~.axes.Axes` instance as a *sharex* or *sharey* keyword argument. - -Changing the axis limits on one Axes will be reflected automatically -in the other, and vice-versa, so when you navigate with the toolbar -the Axes will follow each other on their shared axis. Ditto for -changes in the axis scaling (e.g., log vs. linear). However, it is -possible to have differences in tick labeling, e.g., you can selectively -turn off the tick labels on one Axes. - -The example below shows how to customize the tick labels on the -various axes. Shared axes share the tick locator, tick formatter, -view limits, and transformation (e.g., log, linear). But the tick labels -themselves do not share properties. This is a feature and not a bug, -because you may want to make the tick labels smaller on the upper -axes, e.g., in the example below. +Use axis sharing when you want to compare data across multiple subplots, and want to +ensure they are on the same scale. To do so, pass ``sharex=True`` and/or ``sharey=True`` +to `~.pyplot.subplots`. + +This ensures the x- or y-axis limits are synchronized across the subplots. Autoscaling +considers the data on all Axes; therefore, any limit changes, including interactive zoom +and pan, will affect all shared axes. + +The plot below illustrates this by showing two different time-series and using *sharex* +to ensure the times are aligned. + +For more info see :ref:`sharing-axes`. + +.. redirect-from:: /gallery/subplots_axes_and_figures/share_axis_lims_views """ import matplotlib.pyplot as plt import numpy as np -t = np.arange(0.01, 5.0, 0.01) -s1 = np.sin(2 * np.pi * t) -s2 = np.exp(-t) -s3 = np.sin(4 * np.pi * t) - -ax1 = plt.subplot(311) -plt.plot(t, s1) -# reduce the fontsize of the tick labels -plt.tick_params('x', labelsize=6) - -# share x only -ax2 = plt.subplot(312, sharex=ax1) -plt.plot(t, s2) -# make these tick labels invisible -plt.tick_params('x', labelbottom=False) - -# share x and y -ax3 = plt.subplot(313, sharex=ax1, sharey=ax1) -plt.plot(t, s3) -plt.xlim(0.01, 5.0) +t1 = np.linspace(0, 8, 201) +y1 = np.sin(2 * np.pi * t1) +t2 = np.linspace(2, 10, 201) +y2 = 20 * np.cos(2 * np.pi * t2)**2 * np.exp(-0.3*t2) + +fig, (ax1, ax2) = plt.subplots(2, sharex=True) + +ax1.plot(t1, y1) +ax1.set_ylabel("Signal 1") + +ax2.plot(t2, y2) +ax2.set_ylabel("Signal 2") +ax2.set_xlabel("Time (s)") + plt.show() # %% diff --git a/galleries/examples/subplots_axes_and_figures/subplots_demo.py b/galleries/examples/subplots_axes_and_figures/subplots_demo.py index 0e3cb1102230..ea38a2483fdd 100644 --- a/galleries/examples/subplots_axes_and_figures/subplots_demo.py +++ b/galleries/examples/subplots_axes_and_figures/subplots_demo.py @@ -108,6 +108,8 @@ ax.label_outer() # %% +# .. _sharing-axes: +# # Sharing axes # """""""""""" # @@ -141,8 +143,18 @@ # %% # For subplots that are sharing axes one set of tick labels is enough. Tick # labels of inner Axes are automatically removed by *sharex* and *sharey*. -# Still there remains an unused empty space between the subplots. -# +# You can selectively restore them using `~.axes.Axes.tick_params`. + +fig, axs = plt.subplots(3, sharex=True, sharey=True) +fig.suptitle('Restored xtick labels on to Axes') +axs[0].plot(x, y ** 2) +axs[1].plot(x, 0.3 * y, 'o') +axs[2].plot(x, y, '+') + +axs[0].tick_params(labelbottom=True) + +# %% +# It is also possible to remove the empty space between the subplots. # To precisely control the positioning of the subplots, one can explicitly # create a `.GridSpec` with `.Figure.add_gridspec`, and then call its # `~.GridSpecBase.subplots` method. For example, we can reduce the height diff --git a/galleries/examples/ticks/colorbar_tick_labelling_demo.py b/galleries/examples/ticks/colorbar_tick_labelling_demo.py index 6436748a46ec..30d2dbe274ce 100644 --- a/galleries/examples/ticks/colorbar_tick_labelling_demo.py +++ b/galleries/examples/ticks/colorbar_tick_labelling_demo.py @@ -1,6 +1,6 @@ """ ======================= -Colorbar Tick Labelling +Colorbar Tick labelling ======================= Vertical colorbars have ticks, tick labels, and labels visible on the *y* axis, diff --git a/galleries/examples/ticks/date_demo_convert.py b/galleries/examples/ticks/date_demo_convert.py index c22edf54df9a..a3c7a25b5fc0 100644 --- a/galleries/examples/ticks/date_demo_convert.py +++ b/galleries/examples/ticks/date_demo_convert.py @@ -1,9 +1,9 @@ """ -================= -Date Demo Convert -================= - +=================== +Date converter demo +=================== """ + import datetime import matplotlib.pyplot as plt @@ -21,14 +21,13 @@ fig, ax = plt.subplots() ax.plot(dates, y**2, 'o') -# this is superfluous, since the autoscaler should get it right, but +# This is superfluous, since the autoscaler should get it right, but # use date2num and num2date to convert between dates and floats if -# you want; both date2num and num2date convert an instance or sequence +# you want; both date2num and num2date convert an instance or sequence. ax.set_xlim(dates[0], dates[-1]) # The hour locator takes the hour or sequence of hours you want to -# tick, not the base multiple - +# tick, not the base multiple. ax.xaxis.set_major_locator(DayLocator()) ax.xaxis.set_minor_locator(HourLocator(range(0, 25, 6))) ax.xaxis.set_major_formatter(DateFormatter('%Y-%m-%d')) diff --git a/galleries/examples/ticks/fig_axes_customize_simple.py b/galleries/examples/ticks/fig_axes_customize_simple.py index 07a569e3d31d..72c36c7a96cc 100644 --- a/galleries/examples/ticks/fig_axes_customize_simple.py +++ b/galleries/examples/ticks/fig_axes_customize_simple.py @@ -1,7 +1,7 @@ """ -========================= -Fig Axes Customize Simple -========================= +====================================== +Customizing figure and axes appearance +====================================== Customize the background, labels and ticks of a simple plot. diff --git a/galleries/examples/ticks/ticklabels_rotation.py b/galleries/examples/ticks/ticklabels_rotation.py index c17312a61d48..f5ab80745630 100644 --- a/galleries/examples/ticks/ticklabels_rotation.py +++ b/galleries/examples/ticks/ticklabels_rotation.py @@ -7,6 +7,9 @@ Adjust the tick properties using `~.Axes.tick_params`: Set the angle in degrees via the *rotation* parameter. Set the *rotation_mode* parameter to "xtick" / "ytick" to make the text point towards the tick, see also `~.Text.set_rotation_mode`. + +Note: We use ``layout="constrained"`` to make sure there is enough space for the tick +labels so that they are not cut off. """ import matplotlib.pyplot as plt @@ -30,7 +33,7 @@ 'Vietnam': 102.3, } -fig, ax = plt.subplots() +fig, ax = plt.subplots(layout="constrained") ax.bar(population.keys(), population.values()) ax.tick_params("x", rotation=45, rotation_mode="xtick") ax.set_ylabel("population (millions)") diff --git a/galleries/tutorials/artists.py b/galleries/tutorials/artists.py index 4f93f7c71a6e..b3440d71fe7f 100644 --- a/galleries/tutorials/artists.py +++ b/galleries/tutorials/artists.py @@ -38,12 +38,7 @@ helper methods to create the primitives. In the example below, we create a ``Figure`` instance using :func:`matplotlib.pyplot.figure`, which is a convenience method for instantiating ``Figure`` instances and connecting them -with your user interface or drawing toolkit ``FigureCanvas``. As we will -discuss below, this is not necessary -- you can work directly with PostScript, -PDF Gtk+, or wxPython ``FigureCanvas`` instances, instantiate your ``Figures`` -directly and connect them yourselves -- but since we are focusing here on the -``Artist`` API we'll let :mod:`~matplotlib.pyplot` handle some of those details -for us:: +with a GUI framework so that they can be shown in a window on the screen:: import matplotlib.pyplot as plt fig = plt.figure() @@ -94,9 +89,8 @@ class in the Matplotlib API, and the one you will be working with most In [102]: line Out[102]: -If you make subsequent calls to ``ax.plot`` (and the hold state is "on" -which is the default) then additional lines will be added to the list. -You can remove a line later by calling its ``remove`` method:: +If you make subsequent calls to ``ax.plot`` then additional lines will be added +to the list. You can remove a line later by calling its ``remove`` method:: line = ax.lines[0] line.remove() @@ -301,7 +295,7 @@ class in the Matplotlib API, and the one you will be working with most # Out[159]: # # In [160]: print(fig.axes) -# [, ] +# [, ] # # Because the figure maintains the concept of the "current Axes" (see # :meth:`Figure.gca ` and @@ -596,40 +590,16 @@ class in the Matplotlib API, and the one you will be working with most # the ticks are placed and how they are represented as strings. # # Each ``Axis`` object contains a :attr:`~matplotlib.axis.Axis.label` attribute -# (this is what :mod:`.pyplot` modifies in calls to `~.pyplot.xlabel` and -# `~.pyplot.ylabel`) as well as a list of major and minor ticks. The ticks are +# (this is what `~.Axes.set_xlabel` / `~.Axes.set_ylabel` modifies internally) +# as well as a list of major and minor ticks. The ticks are # `.axis.XTick` and `.axis.YTick` instances, which contain the actual line and # text primitives that render the ticks and ticklabels. Because the ticks are -# dynamically created as needed (e.g., when panning and zooming), you should -# access the lists of major and minor ticks through their accessor methods -# `.axis.Axis.get_major_ticks` and `.axis.Axis.get_minor_ticks`. Although -# the ticks contain all the primitives and will be covered below, ``Axis`` -# instances have accessor methods that return the tick lines, tick labels, tick -# locations etc.: - -fig, ax = plt.subplots() -axis = ax.xaxis -axis.get_ticklocs() - -# %% - -axis.get_ticklabels() - -# %% -# note there are twice as many ticklines as labels because by default there are -# tick lines at the top and bottom but only tick labels below the xaxis; -# however, this can be customized. - -axis.get_ticklines() - -# %% -# And with the above methods, you only get lists of major ticks back by -# default, but you can also ask for the minor ticks: - -axis.get_ticklabels(minor=True) -axis.get_ticklines(minor=True) - -# %% +# dynamically created and modified as needed (e.g., when panning and zooming), +# directly working on the ticks and their parts (tick lines, tick labels, grid lines) +# is discouraged. Instead, the high-level concepts tick locators, tick formatters +# and style configuration via `~.Axes.tick_params` should be used. See +# :ref:`user_axes_ticks` for details. +# # Here is a summary of some of the useful accessor methods of the ``Axis`` # (these have corresponding setters where useful, such as # :meth:`~matplotlib.axis.Axis.set_major_formatter`.) @@ -640,82 +610,16 @@ class in the Matplotlib API, and the one you will be working with most # `~.Axis.get_scale` The scale of the Axis, e.g., 'log' or 'linear' # `~.Axis.get_view_interval` The interval instance of the Axis view limits # `~.Axis.get_data_interval` The interval instance of the Axis data limits -# `~.Axis.get_gridlines` A list of grid lines for the Axis # `~.Axis.get_label` The Axis label - a `.Text` instance -# `~.Axis.get_offset_text` The Axis offset text - a `.Text` instance -# `~.Axis.get_ticklabels` A list of `.Text` instances - -# keyword minor=True|False -# `~.Axis.get_ticklines` A list of `.Line2D` instances - -# keyword minor=True|False -# `~.Axis.get_ticklocs` A list of Tick locations - -# keyword minor=True|False # `~.Axis.get_major_locator` The `.ticker.Locator` instance for major ticks # `~.Axis.get_major_formatter` The `.ticker.Formatter` instance for major # ticks # `~.Axis.get_minor_locator` The `.ticker.Locator` instance for minor ticks # `~.Axis.get_minor_formatter` The `.ticker.Formatter` instance for minor # ticks -# `~.axis.Axis.get_major_ticks` A list of `.Tick` instances for major ticks -# `~.axis.Axis.get_minor_ticks` A list of `.Tick` instances for minor ticks +# `~.Axis.get_tick_params` Styling of ticks, ticklabels and gridlines # `~.Axis.grid` Turn the grid on or off for the major or minor # ticks # ============================= ============================================== # -# Here is an example, not recommended for its beauty, which customizes -# the Axes and Tick properties. - -# plt.figure creates a matplotlib.figure.Figure instance -fig = plt.figure() -rect = fig.patch # a rectangle instance -rect.set_facecolor('lightgoldenrodyellow') - -ax1 = fig.add_axes((0.1, 0.3, 0.4, 0.4)) -rect = ax1.patch -rect.set_facecolor('lightslategray') - - -for label in ax1.xaxis.get_ticklabels(): - # label is a Text instance - label.set_color('red') - label.set_rotation(45) - label.set_fontsize(16) - -for line in ax1.yaxis.get_ticklines(): - # line is a Line2D instance - line.set_color('green') - line.set_markersize(25) - line.set_markeredgewidth(3) - -plt.show() - -# %% -# .. _tick-container: -# -# Tick containers -# --------------- -# -# The :class:`matplotlib.axis.Tick` is the final container object in our -# descent from the :class:`~matplotlib.figure.Figure` to the -# :class:`~matplotlib.axes.Axes` to the :class:`~matplotlib.axis.Axis` -# to the :class:`~matplotlib.axis.Tick`. The ``Tick`` contains the tick -# and grid line instances, as well as the label instances for the upper -# and lower ticks. Each of these is accessible directly as an attribute -# of the ``Tick``. -# -# ============== ========================================================== -# Tick attribute Description -# ============== ========================================================== -# tick1line A `.Line2D` instance -# tick2line A `.Line2D` instance -# gridline A `.Line2D` instance -# label1 A `.Text` instance -# label2 A `.Text` instance -# ============== ========================================================== -# -# Here is an example which sets the formatter for the right side ticks with -# dollar signs and colors them green on the right side of the yaxis. -# -# -# .. include:: ../gallery/ticks/dollar_ticks.rst -# :start-after: .. redirect-from:: /gallery/pyplots/dollar_ticks -# :end-before: .. admonition:: References +# The full Axis API can be found at :doc:`/api/axis_api`. diff --git a/galleries/users_explain/axes/autoscale.py b/galleries/users_explain/axes/autoscale.py index 337960302c38..22cdd1f8dc96 100644 --- a/galleries/users_explain/axes/autoscale.py +++ b/galleries/users_explain/axes/autoscale.py @@ -6,33 +6,41 @@ Axis autoscaling ================ -The limits on an axis can be set manually (e.g. ``ax.set_xlim(xmin, xmax)``) -or Matplotlib can set them automatically based on the data already on the Axes. -There are a number of options to this autoscaling behaviour, discussed below. -""" +Basic concept +------------- -# %% -# We will start with a simple line plot showing that autoscaling -# extends the axis limits 5% beyond the data limits (-2π, 2π). +Autoscaling ensures that data is visible within the Axes by automatically adjusting +the axis limits. When you plot data, Matplotlib's autoscaling mechanism updates the +axis limits accordingly. +""" import matplotlib.pyplot as plt import numpy as np -x = np.linspace(-2 * np.pi, 2 * np.pi, 100) +x = np.linspace(-6, 6, 201) y = np.sinc(x) fig, ax = plt.subplots() ax.plot(x, y) # %% +# +# .. _autoscale_margins: +# # Margins # ------- -# The default margin around the data limits is 5%, which is based on the -# default configuration setting of :rc:`axes.xmargin`, :rc:`axes.ymargin`, -# and :rc:`axes.zmargin`: +# To ensure that the data is not at the very edge of the plot, Matplotlib adds a +# margin around the data limits. Note that the *x* data range in the above plot is +# [-6, 6], but the x-axis limits are slightly wider due to the margin. +# +# The default margin is 5%, defined via +# +# - :rc:`axes.xmargin` +# - :rc:`axes.ymargin` +# - :rc:`axes.zmargin` -print(ax.margins()) +print(ax.get_xmargin(), ax.get_ymargin()) # %% # The margin size can be overridden to make them smaller or larger using @@ -54,10 +62,13 @@ ax.margins(y=-0.2) # %% +# +# .. _autoscale_sticky_edges: +# # Sticky edges # ------------ # There are plot elements (`.Artist`\s) that are usually used without margins. -# For example false-color images (e.g. created with `.Axes.imshow`) are not +# For example, false-color images (e.g. created with `.Axes.imshow`) are not # considered in the margins calculation. # @@ -116,14 +127,14 @@ ax[1].set_title("Two curves") # %% -# However, there are cases when you don't want to automatically adjust the -# viewport to new data. +# If you don't want automatic updates of the axis limits, either deactivate +# autoscaling with `~.axes.Axes.autoscale` or set the limits +# manually with `~.axes.Axes.set_xlim` / `~.axes.Axes.set_ylim`. # -# One way to disable autoscaling is to manually set the -# axis limit. Let's say that we want to see only a part of the data in +# Let's say that we want to see only a part of the data in # greater detail. Setting the ``xlim`` persists even if we add more curves to -# the data. To recalculate the new limits calling `.Axes.autoscale` will -# toggle the functionality manually. +# the data. Calling `.Axes.autoscale` will re-enable the autoscaling and +# recalculate the limits to fit all the data. fig, ax = plt.subplots(ncols=2, figsize=(12, 8)) ax[0].plot(x, y) @@ -158,3 +169,152 @@ ax.autoscale(enable=None, axis="x", tight=True) print(ax.margins()) + +# %% +# Technical background +# -------------------- +# +# This section explains the internal pipeline that runs when autoscaling +# computes axis limits from data. Understanding the mechanics helps when +# you encounter surprising behaviour or need to update limits manually. +# +# Data limits and view limits +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# Matplotlib maintains two sets of limits: +# +# - **Data limits** (`.Axes.dataLim`): the tight bounding box of the raw data. +# - **View limits** (`.Axes.viewLim`): the displayed axis limits. By default, +# computed from the data limits through the autoscaling mechanism outlined +# below, but they can be set independently. View limits can alternatively +# be set explicitly through `~.axes.Axes.set_xlim` / `~.axes.Axes.set_ylim`, +# which also disables autoscaling so that the set limits remain fixed. +# +# The following shows the input and output of this process — ``dataLim`` holds +# the raw data bounds, ``viewLim`` the final displayed axis limits. + + +fig, ax = plt.subplots() +x = np.linspace(-6, 6, 201) +y = np.sin(x) +ax.plot(x, y) +print(f"dataLim x: ({ax.dataLim.x0:.3f}, {ax.dataLim.x1:.3f})") +print(f"dataLim y: ({ax.dataLim.y0:.3f}, {ax.dataLim.y1:.3f})") +print(f"viewLim x: ({ax.viewLim.x0:.3f}, {ax.viewLim.x1:.3f})") +print(f"viewLim y: ({ax.viewLim.y0:.3f}, {ax.viewLim.y1:.3f})") + +# %% +# The x data range is [-6, 6] and the default 5% margin adds roughly 0.6 on +# each side, widening the view to about [-6.6, 6.6]. The same applies to the +# y axis. +# +# Update logic +# ~~~~~~~~~~~~ +# +# Data and view limit updates are handled as separate stages. +# +# **Data limits**: When an artist is added to an Axes through one of the +# plotting methods, the data limits are updated through `.Axes.update_datalim` +# to include the new data. This only ever increases the data limits. It is +# also possible to update `.Axes.dataLim` manually, but this is not common. +# Removal of an artist or change of its data does not trigger any update of +# the data limits, so they can become out of date. In such cases, it is +# necessary to explicitly recompute the data limit through `.Axes.relim`. +# +# **View limits**: When autoscaling is enabled, the view limits are +# automatically computed from the data limit. This update is lazy and only +# triggered when the view limits are queried or drawn, so that they don't have +# to be recomputed for every added artist. This is transparent to the user. +# Explicit changes of the data limits through `.Axes.dataLim` or `.Axes.relim` +# do not trigger an update of the view limits, so they can also become out of +# date. In such cases, it is necessary to explicitly recompute the view limits +# through `.Axes.autoscale_view`. +# +# View limit calculation +# ~~~~~~~~~~~~~~~~~~~~~~ +# +# Given the data limits, the view limits are derived through these steps: +# +# - scale domain clamping +# - margin expansion +# - sticky edge clamping +# - optional limit rounding +# +# Scale domain clamping +# ~~~~~~~~~~~~~~~~~~~~~ +# +# Before margins are applied, the data limits are clipped to the valid domain +# of the axis scale. This matters for scales like log (positive values only) +# and logit (values strictly between 0 and 1): if a bound lies outside the +# domain, it is replaced with a value at the domain boundary. +# +# For this purpose, `.Axes.dataLim` tracks not just the ordinary min/max of +# the data but also ``minpos`` — the smallest strictly positive value seen. +# A log-scale lower bound of zero or less is replaced with ``minpos`` rather +# than the actual minimum, because only positive values can be displayed. +# +# For a logit scale, the upper bound is approximated as ``1 - minpos``, since +# the largest data value below 1 is not tracked separately. This means the +# autoscaled upper limit may include slightly more headroom than necessary +# when the data maximum is well below 1. +# +# Margin expansion +# ~~~~~~~~~~~~~~~~ +# +# The first step is to apply the margins, i.e. widen the view limits beyond the +# data limits so that data is not at the very edge of the plot. Margins are +# specified as a fraction of the data span in screen coordinates so that +# the data-free border area always has the same visual size, irrespective of +# data ranges or axis scales. The margin is applied symmetrically to both sides +# of the data limits, so the view is expanded equally in both directions. +# +# This is illustrated in the following example, where the data limits and +# axis scales are different, but the visual margin is the same in both cases. + +fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(9, 4)) +fig.suptitle("Margins are visually constant, " + "even with different data limits and axis scales") + +ax1.plot([0, 10], [0, 1]) +ax1.margins(0.2) + +x = np.linspace(1, 20) +ax2.semilogy(x, np.exp(x)) +ax2.margins(0.2) + +# %% +# Sticky edges clamping +# ~~~~~~~~~~~~~~~~~~~~~ +# +# Sticky edges are axis values at which margin expansion is clamped. After +# computing the margin-expanded limits, if an expanded limit would extend +# beyond a sticky edge, it is pulled back to that edge instead. +# +# Artists register sticky edges to prevent blank margins at natural data +# boundaries. `~.Axes.imshow`, for example, registers sticky edges at its +# four pixel boundaries, which is why images fill the Axes by default without +# any surrounding margin (as shown in the :ref:`autoscale_sticky_edges` +# section above). Sticky edges only suppress *outward expansion past the data +# boundary* — they never shrink limits into the data, and negative margins +# are not affected. Setting ``Axes.use_sticky_edges = False`` disables sticky +# edge clamping on that Axes. +# +# Limit rounding +# ~~~~~~~~~~~~~~ +# +# As a final step, the view limits can optionally be expanded outward to the +# nearest "nice" tick position, so that the axis edges coincide with tick +# marks. This is disabled by default, but can be turned on with the +# "round_numbers" mode of :rc:`axes.autolimit_mode`: +# +# - ``'data'`` (default): keep the limits at the margin-expanded values. +# - ``'round_numbers'``: expand the limits outward to the nearest "nice" tick +# position, so the axis edges coincide with tick marks. + +fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(10, 4)) +ax1.plot([0.3, 4.7], [0.3, 4.7]) +ax1.set_title("autolimit_mode='data' (default)") +with plt.rc_context({'axes.autolimit_mode': 'round_numbers'}): + ax2.plot([0.3, 4.7], [0.3, 4.7]) + ax2.set_title("autolimit_mode='round_numbers'") + ax2.autoscale_view() # force autoscale while round_numbers is active diff --git a/lib/matplotlib/_api/__init__.pyi b/lib/matplotlib/_api/__init__.pyi index 0bcce210634f..58dee136e233 100644 --- a/lib/matplotlib/_api/__init__.pyi +++ b/lib/matplotlib/_api/__init__.pyi @@ -19,6 +19,7 @@ from .deprecation import ( # noqa: F401, re-exported API _T = TypeVar("_T") class _Unset: ... +UNSET = _Unset() class classproperty(Any): def __init__( @@ -41,9 +42,7 @@ def check_isinstance( def list_suggestion_error_msg(name: str, potential: Any, values: Sequence[Any]) -> str: ... def check_in_list(values: Sequence[Any], /, **kwargs: Any) -> None: ... def check_shape(shape: tuple[int | None, ...], /, **kwargs: NDArray) -> None: ... -def getitem_checked( - mapping: Mapping[Any, _T], /, _error_cls: type[Exception], **kwargs: Any -) -> _T: ... +def getitem_checked(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/_constrained_layout.py b/lib/matplotlib/_constrained_layout.py index 33ec8ef985e7..ce488d555898 100644 --- a/lib/matplotlib/_constrained_layout.py +++ b/lib/matplotlib/_constrained_layout.py @@ -539,14 +539,10 @@ def match_submerged_margins(layoutgrids, fig): # interior columns: if len(ss1.colspan) > 1: - maxsubl = np.max( - lg1.margin_vals['left'][ss1.colspan[1:]] + - lg1.margin_vals['leftcb'][ss1.colspan[1:]] - ) - maxsubr = np.max( - lg1.margin_vals['right'][ss1.colspan[:-1]] + - lg1.margin_vals['rightcb'][ss1.colspan[:-1]] - ) + leftcb = lg1.margin_vals['leftcb'][ss1.colspan[1:]] + rightcb = lg1.margin_vals['rightcb'][ss1.colspan[:-1]] + maxsubl = np.max(lg1.margin_vals['left'][ss1.colspan[1:]] + leftcb) + maxsubr = np.max(lg1.margin_vals['right'][ss1.colspan[:-1]] + rightcb) for ax2 in axs: ss2 = ax2.get_subplotspec() lg2 = layoutgrids[ss2.get_gridspec()] @@ -561,22 +557,17 @@ def match_submerged_margins(layoutgrids, fig): lg2.margin_vals['rightcb'][ss2.colspan[:-1]]) if maxsubr2 > maxsubr: maxsubr = maxsubr2 - for i in ss1.colspan[1:]: - lg1.edit_margin_min('left', maxsubl, cell=i) - for i in ss1.colspan[:-1]: - lg1.edit_margin_min('right', maxsubr, cell=i) + for i, cb in zip(ss1.colspan[1:], leftcb): + lg1.edit_margin_min('left', maxsubl - cb, cell=i) + for i, cb in zip(ss1.colspan[:-1], rightcb): + lg1.edit_margin_min('right', maxsubr - cb, cell=i) # interior rows: if len(ss1.rowspan) > 1: - maxsubt = np.max( - lg1.margin_vals['top'][ss1.rowspan[1:]] + - lg1.margin_vals['topcb'][ss1.rowspan[1:]] - ) - maxsubb = np.max( - lg1.margin_vals['bottom'][ss1.rowspan[:-1]] + - lg1.margin_vals['bottomcb'][ss1.rowspan[:-1]] - ) - + topcb = lg1.margin_vals['topcb'][ss1.rowspan[1:]] + bottomcb = lg1.margin_vals['bottomcb'][ss1.rowspan[:-1]] + maxsubt = np.max(lg1.margin_vals['top'][ss1.rowspan[1:]] + topcb) + maxsubb = np.max(lg1.margin_vals['bottom'][ss1.rowspan[:-1]] + bottomcb) for ax2 in axs: ss2 = ax2.get_subplotspec() lg2 = layoutgrids[ss2.get_gridspec()] @@ -590,10 +581,10 @@ def match_submerged_margins(layoutgrids, fig): lg2.margin_vals['bottom'][ss2.rowspan[:-1]] + lg2.margin_vals['bottomcb'][ss2.rowspan[:-1]] ), maxsubb]) - for i in ss1.rowspan[1:]: - lg1.edit_margin_min('top', maxsubt, cell=i) - for i in ss1.rowspan[:-1]: - lg1.edit_margin_min('bottom', maxsubb, cell=i) + for i, cb in zip(ss1.rowspan[1:], topcb): + lg1.edit_margin_min('top', maxsubt - cb, cell=i) + for i, cb in zip(ss1.rowspan[:-1], bottomcb): + lg1.edit_margin_min('bottom', maxsubb - cb, cell=i) return axs diff --git a/lib/matplotlib/_mathtext.py b/lib/matplotlib/_mathtext.py index b04386e7666a..9f23e5e3ab08 100644 --- a/lib/matplotlib/_mathtext.py +++ b/lib/matplotlib/_mathtext.py @@ -1010,7 +1010,7 @@ class FontConstantsBase: class ComputerModernFontConstants(FontConstantsBase): # Previously, the x-height of Computer Modern was obtained from the font - # table. However, that x-height was greater than the the actual (rendered) + # table. However, that x-height was greater than the actual (rendered) # x-height by a factor of 1.771484375 (at font size 12, DPI 100 and hinting # type 32). Now that we're using the rendered x-height, some font constants # have been increased by the same factor to compensate. @@ -1721,8 +1721,8 @@ def ship(box: Box, xy: tuple[float, float] = (0, 0)) -> Output: off_h = ox off_v = oy + box.height output = Output(box) - phantom: list[bool] = [] + def render(node, *args): if not any(phantom): node.render(*args) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 66770e426386..dd1eb8433888 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -41,6 +41,7 @@ BarContainer, ErrorbarContainer, PieContainer, StemContainer) from matplotlib.text import Text from matplotlib.transforms import _ScaledRotation +from matplotlib._api import UNSET as _UNSET _log = logging.getLogger(__name__) @@ -2409,11 +2410,16 @@ def bar(self, x, height, width=0.8, bottom=None, *, align="center", errors. - *None*: No errorbar. (Default) - See :doc:`/gallery/statistics/errorbar_features` for an example on + This is a convenience shortcut for an extra `~.axes.Axes.errorbar` + call. See its documentation and + :doc:`/gallery/statistics/errorbar_features` for an example on the usage of *xerr* and *yerr*. ecolor : :mpltype:`color` or list of :mpltype:`color`, default: 'black' The line color of the errorbars. + Multiple colors are only supported if the errorbars do not have + caps. If you need individually colored errorbars with caps, instead + use explicit `~.axes.Axes.errorbar` calls for each data point. capsize : float, default: :rc:`errorbar.capsize` The length of the error bar caps in points. @@ -3534,13 +3540,13 @@ def stem(self, *args, linefmt=None, markerfmt=None, basefmt=None, bottom=0, self.add_container(stem_container) return stem_container - @_api.make_keyword_only("3.10", "explode") - @_preprocess_data(replace_names=["x", "explode", "labels", "colors"]) - def pie(self, x, explode=None, labels=None, colors=None, - autopct=None, pctdistance=0.6, shadow=False, labeldistance=1.1, - startangle=0, radius=1, counterclock=True, - wedgeprops=None, textprops=None, center=(0, 0), - frame=False, rotatelabels=False, *, normalize=True, hatch=None): + @_preprocess_data(replace_names=["x", "explode", "labels", "colors", + "wedge_labels"]) + def pie(self, x, *, explode=None, labels=None, colors=None, wedge_labels=None, + wedge_label_distance=0.6, autopct=None, pctdistance=0.6, shadow=False, + labeldistance=_UNSET, startangle=0, radius=1, counterclock=True, + wedgeprops=None, textprops=None, center=(0, 0), frame=False, + rotatelabels=False, normalize=True, hatch=None): """ Plot a pie chart. @@ -3560,7 +3566,13 @@ def pie(self, x, explode=None, labels=None, colors=None, of the radius with which to offset each wedge. labels : list, default: None - A sequence of strings providing the labels for each wedge + A sequence of strings providing the legend labels for each wedge. + + .. deprecated:: 3.12 + In future these labels will not appear on the wedges but only + be made available for the legend (see *labeldistance* below). + To place labels on the wedges, use *wedge_labels* or the + `pie_label` method. colors : :mpltype:`color` or list of :mpltype:`color`, default: None A sequence of colors through which the pie chart will cycle. If @@ -3573,12 +3585,35 @@ def pie(self, x, explode=None, labels=None, colors=None, .. versionadded:: 3.7 + wedge_labels : str or list of str, optional + A sequence of strings providing the labels for each wedge, or a format + string with ``absval`` and/or ``frac`` placeholders. For example, to label + each wedge with its value and the percentage in brackets:: + + wedge_labels="{absval:d} ({frac:.0%})" + + For more control or to add multiple sets of labels, use `pie_label` + instead. + + .. versionadded:: 3.12 + + wedge_label_distance : float, default: 0.6 + The radial position of the wedge labels, relative to the pie radius. + Values > 1 are outside the wedge and values < 1 are inside the wedge. + + .. versionadded:: 3.12 + autopct : None or str or callable, default: None If not *None*, *autopct* is a string or function used to label the wedges with their numeric value. The label will be placed inside the wedge. If *autopct* is a format string, the label will be ``fmt % pct``. If *autopct* is a function, then it will be called. + .. admonition:: Discouraged + + Consider using the *wedge_labels* parameter or `pie_label` + method instead. + pctdistance : float, default: 0.6 The relative distance along the radius at which the text generated by *autopct* is drawn. To draw the text outside the pie, @@ -3591,6 +3626,11 @@ def pie(self, x, explode=None, labels=None, colors=None, If set to ``None``, labels are not drawn but are still stored for use in `.legend`. + .. deprecated:: 3.12 + From v3.14 *labeldistance* will default to ``None`` and will + later be removed altogether. Use *wedge_labels* and + *wedge_label_distance* or the `pie_label` method instead. + shadow : bool or dict, default: False If bool, whether to draw a shadow beneath the pie. If dict, draw a shadow passing the properties in the dict to `.Shadow`. @@ -3672,8 +3712,33 @@ def pie(self, x, explode=None, labels=None, colors=None, raise ValueError('Cannot plot an unnormalized pie with sum(x) > 1') else: fracs = x + + if labeldistance is _UNSET: + # NB: when the labeldistance default changes, both labeldistance and + # rotatelabels should be deprecated for removal. + if labels is not None: + msg = ( + "From %(removal)s labeldistance will default to None, so that the " + "strings provided in the labels parameter are only available for " + "the legend. Later labeldistance will be removed completely. To " + "preserve existing behavior for now, pass labeldistance=1.1. " + "Consider using the wedge_labels parameter or the pie_label method " + "instead of the labels parameter." + ) + _api.warn_deprecated("3.12", message=msg) + labeldistance = 1.1 + if labels is None: labels = [''] * len(x) + else: + if wedge_labels is not None and labeldistance is not None: + raise ValueError( + 'wedge_labels is a replacement for labels when annotating the ' + 'wedges, so the two should not be used together unless ' + 'labeldistance is None. To add multiple sets of labels, use the ' + 'pie_label method.' + ) + if explode is None: explode = [0] * len(x) if len(x) != len(labels): @@ -3731,11 +3796,16 @@ def get_next_color(): pc = PieContainer(slices, x, normalize) - if labeldistance is None: + if wedge_labels is not None: + self.pie_label(pc, wedge_labels, distance=wedge_label_distance, + textprops=textprops) + + elif labeldistance is None: # Insert an empty list of texts for backwards compatibility of the # return value. pc.add_texts([]) - else: + + if labeldistance is not None: # Add labels to the wedges. labels_textprops = { 'fontsize': mpl.rcParams['xtick.labelsize'], @@ -4302,7 +4372,6 @@ def apply_mask(arrays, mask): @_api.make_keyword_only("3.10", "notch") @_preprocess_data() - @_api.rename_parameter("3.9", "labels", "tick_labels") def boxplot(self, x, notch=None, sym=None, vert=None, orientation='vertical', whis=None, positions=None, widths=None, patch_artist=None, bootstrap=None, @@ -4444,8 +4513,7 @@ def boxplot(self, x, notch=None, sym=None, vert=None, values. .. versionchanged:: 3.9 - Renamed from *labels*, which is deprecated since 3.9 - and will be removed in 3.11. + Renamed from *labels*, which is also removed in 3.11. manage_ticks : bool, default: True If True, the tick locations and labels will be adjusted to match @@ -7759,8 +7827,12 @@ def stairs(self, values, edges=None, *, if edges is None: edges = np.arange(len(values) + 1) - edges, values, baseline = self._process_unit_info( - [("x", edges), ("y", values), ("y", baseline)], kwargs) + if orientation == "vertical": + edges, values, baseline = self._process_unit_info( + [("x", edges), ("y", values), ("y", baseline)], kwargs) + else: + edges, values, baseline = self._process_unit_info( + [("y", edges), ("x", values), ("x", baseline)], kwargs) patch = mpatches.StepPatch(values, edges, diff --git a/lib/matplotlib/axes/_axes.pyi b/lib/matplotlib/axes/_axes.pyi index 1c3e1e560d07..b0871167dd75 100644 --- a/lib/matplotlib/axes/_axes.pyi +++ b/lib/matplotlib/axes/_axes.pyi @@ -31,6 +31,7 @@ import matplotlib.tri as mtri import matplotlib.table as mtable import matplotlib.stackplot as mstack import matplotlib.streamplot as mstream +from matplotlib._api import _Unset import PIL.Image from collections.abc import Callable, Iterable, Sequence @@ -310,10 +311,12 @@ class Axes(_AxesBase): explode: ArrayLike | None = ..., labels: Sequence[str] | None = ..., colors: ColorType | Sequence[ColorType] | None = ..., + wedge_labels: str | Sequence | None = ..., + wedge_label_distance: float | Sequence = ..., autopct: str | Callable[[float], str] | None = ..., pctdistance: float = ..., shadow: bool = ..., - labeldistance: float | None = ..., + labeldistance: float | None | _Unset = ..., startangle: float = ..., radius: float = ..., counterclock: bool = ..., diff --git a/lib/matplotlib/axes/_base.py b/lib/matplotlib/axes/_base.py index ee933ea138ad..d2589cfc74d3 100644 --- a/lib/matplotlib/axes/_base.py +++ b/lib/matplotlib/axes/_base.py @@ -1435,6 +1435,11 @@ def __clear(self): self.xaxis.set_clip_path(self.patch) self.yaxis.set_clip_path(self.patch) + # Lazy tick lists no longer trigger spine transform setup as a + # side effect, so nudge each spine explicitly. + for spine in self.spines.values(): + spine._ensure_transform_is_set() + if self._sharex is not None: self.xaxis.set_visible(xaxis_visible) self.patch.set_visible(patch_visible) @@ -2787,6 +2792,13 @@ def set_autoscale_on(self, b): Parameters ---------- b : bool + + See Also + -------- + :ref:`autoscale` + matplotlib.axes.Axes.autoscale + matplotlib.axes.Axes.set_autoscalex_on + matplotlib.axes.Axes.set_autoscaley_on """ for axis in self._axis_map.values(): axis._set_autoscale_on(b) @@ -2825,6 +2837,7 @@ def get_xmargin(self): See Also -------- + :ref:`autoscale_margins` matplotlib.axes.Axes.set_xmargin """ return self._xmargin @@ -2841,6 +2854,7 @@ def get_ymargin(self): See Also -------- + :ref:`autoscale_margins` matplotlib.axes.Axes.set_ymargin """ return self._ymargin @@ -2860,6 +2874,13 @@ def set_xmargin(self, m): Parameters ---------- m : float greater than -0.5 + + See Also + -------- + :ref:`autoscale_margins` + matplotlib.axes.Axes.margins + matplotlib.axes.Axes.get_xmargin + """ if m <= -0.5: raise ValueError("margin must be greater than -0.5") @@ -2882,6 +2903,12 @@ def set_ymargin(self, m): Parameters ---------- m : float greater than -0.5 + + See Also + -------- + :ref:`autoscale_margins` + matplotlib.axes.Axes.margins + matplotlib.axes.Axes.get_ymargin """ if m <= -0.5: raise ValueError("margin must be greater than -0.5") @@ -2945,6 +2972,7 @@ def margins(self, *margins, x=None, y=None, tight=True): See Also -------- + :ref:`autoscale_margins` .Axes.set_xmargin, .Axes.set_ymargin """ @@ -2999,10 +3027,12 @@ def autoscale(self, enable=True, axis='both', tight=None): """ Autoscale the axis view to the data (toggle). - Convenience method for simple axis view autoscaling. - It turns autoscaling on or off, and then, - if autoscaling for either axis is on, it performs - the autoscaling on the specified axis or Axes. + Convenience method for simple axis view autoscaling. This: + + - Turns autoscaling on or off (`~.axes.Axes.set_autoscalex_on` / + `~.axes.Axes.set_autoscaley_on`). + - Ensures that view limits will get updated when needed. - As view limits + are lazy-updated, this technically marks the view limits as stale. Parameters ---------- @@ -3016,6 +3046,13 @@ def autoscale(self, enable=True, axis='both', tight=None): If True, first set the margins to zero. Then, this argument is forwarded to `~.axes.Axes.autoscale_view` (regardless of its value); see the description of its behavior there. + + See Also + -------- + :ref:`autoscale` + matplotlib.axes.Axes.set_autoscale_on + matplotlib.axes.Axes.set_autoscalex_on + matplotlib.axes.Axes.set_autoscaley_on """ if enable is None: scalex = True diff --git a/lib/matplotlib/axis.py b/lib/matplotlib/axis.py index 0ddfee2d537c..223eb5e7a34f 100644 --- a/lib/matplotlib/axis.py +++ b/lib/matplotlib/axis.py @@ -2,6 +2,7 @@ Classes for the ticks and x- and y-axis. """ +import contextlib import datetime import functools import logging @@ -243,6 +244,25 @@ def set_clip_path(self, path, transform=None): self.gridline.set_clip_path(path, transform) self.stale = True + def _configure_for_axis(self, axis, major): + """ + Apply axis-level configuration to a freshly-materialized Tick. + + Used by `_LazyTickList` to apply ``set_tick_params()`` overrides + held on the Axis and to stamp the clip state set via + ``Axis.set_clip_path`` onto the Tick and its gridline. + """ + # Subclasses of Axis (e.g. SkewXAxis in the skewt gallery example) + # may override _get_tick() without forwarding _{major,minor}_tick_kw, + # so apply them here. + tick_kw = axis._major_tick_kw if major else axis._minor_tick_kw + if tick_kw: + self._apply_params(**tick_kw) + for artist in (self, self.gridline): + artist.clipbox = axis.clipbox + artist._clippath = axis._clippath + artist._clipon = axis._clipon + def contains(self, mouseevent): """ Test whether the mouse event occurred in the Tick marks. @@ -536,6 +556,26 @@ def formatter(self, formatter): self._formatter = formatter +@contextlib.contextmanager +def _rc_context_raw(snapshot): + """ + Like ``mpl.rc_context(snapshot)`` but bypasses ``RcParams`` validators + on entry and exit; re-applying a snapshot to its own values must not + re-trigger one-shot validator warnings (e.g. ``toolbar='toolmanager'``). + ``snapshot=None`` is a no-op. + """ + if snapshot is None: + yield + return + rc = mpl.rcParams + orig = dict(rc) + rc._update_raw(snapshot) + try: + yield + finally: + rc._update_raw(orig) + + class _LazyTickList: """ A descriptor for lazy instantiation of tick lists. @@ -548,26 +588,26 @@ def __init__(self, major): self._major = major def __get__(self, instance, owner): + """Materialize the descriptor to a list with one configured tick.""" if instance is None: return self - else: - # instance._get_tick() can itself try to access the majorTicks - # attribute (e.g. in certain projection classes which override - # e.g. get_xaxis_text1_transform). In order to avoid infinite - # recursion, first set the majorTicks on the instance temporarily - # to an empty list. Then create the tick; note that _get_tick() - # may call reset_ticks(). Therefore, the final tick list is - # created and assigned afterwards. - if self._major: - instance.majorTicks = [] - tick = instance._get_tick(major=True) - instance.majorTicks = [tick] - return instance.majorTicks - else: - instance.minorTicks = [] - tick = instance._get_tick(major=False) - instance.minorTicks = [tick] - return instance.minorTicks + # 1. Bind a placeholder so reentrant access via _get_tick() (e.g. + # projections overriding get_xaxis_text1_transform) does not + # recurse back into this descriptor. + # 2. Build the tick under the rcParams snapshot from the last + # Axis.clear() so its sub-artists pick up the right rcParams. + # 3. Apply set_tick_params() overrides and axis state. + # 4. Re-bind the final list; _get_tick() may have called + # reset_ticks(), which pops the attribute, so this assignment + # is what makes future accesses skip the descriptor. + attr = 'majorTicks' if self._major else 'minorTicks' + setattr(instance, attr, ()) # placeholder; not appended to + with _rc_context_raw(instance._tick_rcParams): + tick = instance._get_tick(major=self._major) + tick._configure_for_axis(instance, self._major) + tick_list = [tick] + setattr(instance, attr, tick_list) + return tick_list class Axis(martist.Artist): @@ -672,6 +712,12 @@ def __init__(self, axes, *, pickradius=15, clear=True): # Initialize here for testing; later add API self._major_tick_kw = dict() self._minor_tick_kw = dict() + # Snapshot of rcParams from the last Axis.clear() (or + # set_tick_params(reset=True)); re-applied by _LazyTickList when + # it lazily materializes a Tick. Kept separate from + # _major_tick_kw/_minor_tick_kw, which hold user-provided + # set_tick_params() overrides rather than ambient rcParams. + self._tick_rcParams = None if clear: self.clear() @@ -838,6 +884,15 @@ def _set_autoscale_on(self, b): Parameters ---------- b : bool + + See Also + -------- + matplotlib.axes.Axes.autoscale + matplotlib.axes.Axes.set_autoscale_on + matplotlib.axes.Axes.get_autoscalex_on + matplotlib.axes.Axes.set_autoscalex_on + matplotlib.axes.Axes.get_autoscaley_on + matplotlib.axes.Axes.set_autoscaley_on """ if b is not None: self._autoscale_on = b @@ -851,12 +906,14 @@ def _reset_major_tick_kw(self): self._major_tick_kw['gridOn'] = ( mpl.rcParams['axes.grid'] and mpl.rcParams['axes.grid.which'] in ('both', 'major')) + self._tick_rcParams = dict(mpl.rcParams) def _reset_minor_tick_kw(self): self._minor_tick_kw.clear() self._minor_tick_kw['gridOn'] = ( mpl.rcParams['axes.grid'] and mpl.rcParams['axes.grid.which'] in ('both', 'minor')) + self._tick_rcParams = dict(mpl.rcParams) def clear(self): """ @@ -887,6 +944,11 @@ def clear(self): # Clear the callback registry for this axis, or it may "leak" self.callbacks = cbook.CallbackRegistry(signals=["units"]) + # Snapshot current rcParams so that a Tick materialized later by + # _LazyTickList (possibly outside any rc_context() active now) + # sees the same rcParams an eager pre-lazy tick would have. + self._tick_rcParams = dict(mpl.rcParams) + # whether the grids are on self._major_tick_kw['gridOn'] = ( mpl.rcParams['axes.grid'] and @@ -907,19 +969,46 @@ def reset_ticks(self): Each list starts with a single fresh Tick. """ - # Restore the lazy tick lists. - try: - del self.majorTicks - except AttributeError: - pass - try: - del self.minorTicks - except AttributeError: - pass - try: - self.set_clip_path(self.axes.patch) - except AttributeError: - pass + # Drop any materialized tick lists so the _LazyTickList descriptor is + # reactivated on next access. If ticks were already materialized, + # re-apply the axes-patch clip path; otherwise skip. + had_major = bool(self.__dict__.pop('majorTicks', None)) + had_minor = bool(self.__dict__.pop('minorTicks', None)) + if had_major or had_minor: + try: + self.set_clip_path(self.axes.patch) + except AttributeError: + pass + + def _existing_ticks(self, major=None): + """ + Yield already-materialized ticks without triggering the lazy descriptor. + + `majorTicks` and `minorTicks` are `_LazyTickList` descriptors that + create a fresh `.Tick` on first access. Several internal methods + (`set_clip_path`, `set_tick_params`) need to touch every + *already-materialized* tick without forcing materialization, because + doing so would + + (a) create throwaway Tick objects during ``Axes.__init__`` and + ``Axes.__clear`` + (b) risk re-entering the + ``Spine.set_position -> Axis.reset_ticks -> Axis.set_clip_path + -> _LazyTickList.__get__ -> Tick.__init__ -> Spine.set_position`` + cascade. + + Reading the instance ``__dict__`` directly bypasses the descriptor. + + Parameters + ---------- + major : bool, optional + If True, yield only major ticks; if False, only minor ticks; + if None (default), yield major followed by minor. + """ + if major is None or major: + yield from self.__dict__.get('majorTicks', ()) + if major is None or not major: + yield from self.__dict__.get('minorTicks', ()) def minorticks_on(self): """ @@ -988,11 +1077,11 @@ def set_tick_params(self, which='major', reset=False, **kwargs): else: if which in ['major', 'both']: self._major_tick_kw.update(kwtrans) - for tick in self.majorTicks: + for tick in self._existing_ticks(major=True): tick._apply_params(**kwtrans) if which in ['minor', 'both']: self._minor_tick_kw.update(kwtrans) - for tick in self.minorTicks: + for tick in self._existing_ticks(major=False): tick._apply_params(**kwtrans) # labelOn and labelcolor also apply to the offset text. if 'label1On' in kwtrans or 'label2On' in kwtrans: @@ -1131,7 +1220,7 @@ def _translate_tick_params(cls, kw, reverse=False): def set_clip_path(self, path, transform=None): super().set_clip_path(path, transform) - for child in self.majorTicks + self.minorTicks: + for child in self._existing_ticks(): child.set_clip_path(path, transform) self.stale = True @@ -1999,7 +2088,9 @@ def _set_formatter(self, formatter, level): if (isinstance(formatter, mticker.FixedFormatter) and len(formatter.seq) > 0 - and not isinstance(level.locator, mticker.FixedLocator)): + and not isinstance(level.locator, mticker.FixedLocator) + and not (hasattr(level.locator, 'base') and + isinstance(level.locator.base, mticker.FixedLocator))): _api.warn_external('FixedFormatter should only be used together ' 'with FixedLocator') @@ -2142,16 +2233,21 @@ def set_ticklabels(self, labels, *, minor=False, fontdict=None, **kwargs): if not labels: # eg labels=[]: formatter = mticker.NullFormatter() - elif isinstance(locator, mticker.FixedLocator): + elif (isinstance(locator, mticker.FixedLocator) or + (hasattr(locator, 'base') and + isinstance(locator.base, mticker.FixedLocator))): + # Also handles locators that wrap a FixedLocator (e.g. RadialLocator). + fixed_locator = (locator if isinstance(locator, mticker.FixedLocator) + else locator.base) # Passing [] as a list of labels is often used as a way to # remove all tick labels, so only error for > 0 labels - if len(locator.locs) != len(labels) and len(labels) != 0: + if len(fixed_locator.locs) != len(labels) and len(labels) != 0: raise ValueError( "The number of FixedLocator locations" - f" ({len(locator.locs)}), usually from a call to" + f" ({len(fixed_locator.locs)}), usually from a call to" " set_ticks, does not match" f" the number of labels ({len(labels)}).") - tickd = {loc: lab for loc, lab in zip(locator.locs, labels)} + tickd = {loc: lab for loc, lab in zip(fixed_locator.locs, labels)} func = functools.partial(self._format_with_dict, tickd) formatter = mticker.FuncFormatter(func) else: diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index 508b744ca04d..27c3752858a7 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -3516,20 +3516,6 @@ def _get_image_filename(self, tool): for filename in [filename, filename + self._icon_extension]: if os.path.isfile(filename): return os.path.abspath(filename) - for fname in [ # Fallback; once deprecation elapses. - tool.image, - tool.image + self._icon_extension, - cbook._get_data_path("images", tool.image), - cbook._get_data_path("images", tool.image + self._icon_extension), - ]: - if os.path.isfile(fname): - _api.warn_deprecated( - "3.9", message=f"Loading icon {tool.image!r} from the current " - "directory or from Matplotlib's image directory. This behavior " - "is deprecated since %(since)s and will be removed in %(removal)s; " - "Tool.image should be set to a path relative to the Tool's source " - "file, or to an absolute path.") - return os.path.abspath(fname) def trigger_tool(self, name): """ diff --git a/lib/matplotlib/backend_bases.pyi b/lib/matplotlib/backend_bases.pyi index a69b36093839..94a8522717cd 100644 --- a/lib/matplotlib/backend_bases.pyi +++ b/lib/matplotlib/backend_bases.pyi @@ -15,7 +15,7 @@ from matplotlib.figure import Figure from matplotlib.font_manager import FontProperties from matplotlib.path import Path from matplotlib.texmanager import TexManager -from matplotlib.text import Text +from matplotlib.text import Text, TextToPath from matplotlib.transforms import Bbox, BboxBase, Transform, TransformedPath from collections.abc import Callable, Iterable, Sequence @@ -40,6 +40,7 @@ def register_backend( def get_registered_canvas_class(format: str) -> type[FigureCanvasBase]: ... class RendererBase: + _text2path: TextToPath def __init__(self) -> None: ... def open_group(self, s: str, gid: str | None = ...) -> None: ... def close_group(self, s: str) -> None: ... diff --git a/lib/matplotlib/backends/_backend_pdf_ps.py b/lib/matplotlib/backends/_backend_pdf_ps.py index a06779b8efee..87fbae4d749b 100644 --- a/lib/matplotlib/backends/_backend_pdf_ps.py +++ b/lib/matplotlib/backends/_backend_pdf_ps.py @@ -18,6 +18,7 @@ if typing.TYPE_CHECKING: + from .font_manager import FontPath from .ft2font import CharacterCodeType, FT2Font, GlyphIndexType from fontTools.ttLib import TTFont @@ -34,7 +35,7 @@ def _cached_get_afm_from_fname(fname): return AFM(fh) -def get_glyphs_subset(fontfile: str, glyphs: set[GlyphIndexType]) -> TTFont: +def get_glyphs_subset(fontfile: FontPath, glyphs: set[GlyphIndexType]) -> TTFont: """ Subset a TTF font. @@ -199,7 +200,7 @@ def __init__(self, subset_size: int = 0): self.subset_size = subset_size def track(self, font: FT2Font, s: str, - features: tuple[str, ...] | None = ..., + features: tuple[str, ...] | None = None, language: str | tuple[tuple[str, int, int], ...] | None = None ) -> list[tuple[int, CharacterCodeType]]: """ diff --git a/lib/matplotlib/backends/backend_cairo.py b/lib/matplotlib/backends/backend_cairo.py index a62890d7c3b1..a16c7a25aec2 100644 --- a/lib/matplotlib/backends/backend_cairo.py +++ b/lib/matplotlib/backends/backend_cairo.py @@ -254,7 +254,10 @@ def _draw_mathtext(self, gc, x, y, s, prop, angle): ctx.new_path() ctx.select_font_face(*_cairo_font_args_from_font_prop(ttfFontProperty(font))) ctx.set_font_size(self.points_to_pixels(fontsize)) - ctx.show_glyphs([(idx, ox, -oy) for _, _, idx, ox, oy in font_glyphs]) + ctx.show_glyphs([ + (glyph_index, ox, -oy) + for _font, _size, _ccode, glyph_index, ox, oy in font_glyphs + ]) for ox, oy, w, h in rects: ctx.new_path() diff --git a/lib/matplotlib/backends/backend_pgf.py b/lib/matplotlib/backends/backend_pgf.py index 3205f294ab2d..36048fe016df 100644 --- a/lib/matplotlib/backends/backend_pgf.py +++ b/lib/matplotlib/backends/backend_pgf.py @@ -154,6 +154,15 @@ def _metadata_to_str(key, value): value = value.name.decode('ascii') else: value = str(value) + + # ensure that metadata does not contain special TeX chars because we + # insert the metadata as raw text into the TeX source + invalid_chars = r"\{}[]()" + if any(c in value + key for c in invalid_chars): + raise ValueError( + f"Invalid metadata value for {key!r}: {value!r}. " + f"The value must not contain the chars {invalid_chars}.") + return f'{key}={{{value}}}' diff --git a/lib/matplotlib/backends/backend_svg.py b/lib/matplotlib/backends/backend_svg.py index 6445915de38b..24790356b9d7 100644 --- a/lib/matplotlib/backends/backend_svg.py +++ b/lib/matplotlib/backends/backend_svg.py @@ -697,7 +697,7 @@ def draw_path(self, gc, path, transform, rgbFace=None): sketch=gc.get_sketch_params()) if gc.get_url() is not None: - self.writer.start('a', {'xlink:href': gc.get_url()}) + self.writer.start('a', {'xlink:href': gc.get_url(), 'target': '_blank'}) self.writer.element('path', d=path_data, **self._get_clip_attrs(gc), style=self._get_style(gc, rgbFace)) if gc.get_url() is not None: @@ -730,7 +730,7 @@ def draw_markers( writer.start('g', **self._get_clip_attrs(gc)) if gc.get_url() is not None: - self.writer.start('a', {'xlink:href': gc.get_url()}) + self.writer.start('a', {'xlink:href': gc.get_url(), 'target': '_blank'}) trans_and_flip = self._make_flip_transform(trans) attrib = {'xlink:href': f'#{oid}'} clip = (0, 0, self.width*72, self.height*72) @@ -788,7 +788,7 @@ def draw_path_collection(self, gc, master_transform, paths, all_transforms, antialiaseds, urls, offset_position, hatchcolors=hatchcolors): url = gc0.get_url() if url is not None: - writer.start('a', attrib={'xlink:href': url}) + writer.start('a', attrib={'xlink:href': url, 'target': '_blank'}) clip_attrs = self._get_clip_attrs(gc0) if clip_attrs: writer.start('g', **clip_attrs) @@ -966,7 +966,7 @@ def draw_image(self, gc, x, y, im, transform=None): url = gc.get_url() if url is not None: - self.writer.start('a', attrib={'xlink:href': url}) + self.writer.start('a', attrib={'xlink:href': url, 'target': '_blank'}) attrib = {} oid = gc.get_gid() @@ -1288,7 +1288,7 @@ def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None): self.writer.start('g', **clip_attrs) if gc.get_url() is not None: - self.writer.start('a', {'xlink:href': gc.get_url()}) + self.writer.start('a', {'xlink:href': gc.get_url(), 'target': '_blank'}) if mpl.rcParams['svg.fonttype'] == 'path': self._draw_text_as_path(gc, x, y, s, prop, angle, ismath, mtext) diff --git a/lib/matplotlib/backends/web_backend/js/mpl.js b/lib/matplotlib/backends/web_backend/js/mpl.js index 7745cbcf1e98..d42fe5e17972 100644 --- a/lib/matplotlib/backends/web_backend/js/mpl.js +++ b/lib/matplotlib/backends/web_backend/js/mpl.js @@ -183,17 +183,7 @@ mpl.figure.prototype._init_canvas = function () { 'z-index: 1;' ); - // Apply a ponyfill if ResizeObserver is not implemented by browser. - if (this.ResizeObserver === undefined) { - if (window.ResizeObserver !== undefined) { - this.ResizeObserver = window.ResizeObserver; - } else { - var obs = _JSXTOOLS_RESIZE_OBSERVER({}); - this.ResizeObserver = obs.ResizeObserver; - } - } - - this.resizeObserverInstance = new this.ResizeObserver(function (entries) { + this.resizeObserverInstance = new ResizeObserver(function (entries) { // There's no need to resize if the WebSocket is not connected: // - If it is still connecting, then we will get an initial resize from // Python once it connects. @@ -728,7 +718,3 @@ mpl.figure.prototype.toolbar_button_onclick = function (name) { mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) { this.message.textContent = tooltip; }; - -///////////////// REMAINING CONTENT GENERATED BY embed_js.py ///////////////// -// prettier-ignore -var _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError("Constructor requires 'new' operator");i.set(this,e)}function h(){throw new TypeError("Function is not a constructor")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line diff --git a/lib/matplotlib/backends/web_backend/package.json b/lib/matplotlib/backends/web_backend/package.json index 95bd8fdf54e6..e2a4009a971b 100644 --- a/lib/matplotlib/backends/web_backend/package.json +++ b/lib/matplotlib/backends/web_backend/package.json @@ -11,8 +11,5 @@ "lint:check": "npm run prettier:check && npm run eslint:check", "prettier": "prettier --write \"**/*{.ts,.tsx,.js,.jsx,.css,.json}\"", "prettier:check": "prettier --check \"**/*{.ts,.tsx,.js,.jsx,.css,.json}\"" - }, - "dependencies": { - "@jsxtools/resize-observer": "^1.0.4" } } diff --git a/lib/matplotlib/colors.py b/lib/matplotlib/colors.py index 685a96cc7803..53471e0f0a17 100644 --- a/lib/matplotlib/colors.py +++ b/lib/matplotlib/colors.py @@ -3522,7 +3522,7 @@ def inverse(self, values): - 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. + is mapped through the corresponding norm. """ values = self._iterable_components_in_data(values, self.n_components) diff --git a/lib/matplotlib/dviread.py b/lib/matplotlib/dviread.py index e41618d67579..979744d1ef5c 100644 --- a/lib/matplotlib/dviread.py +++ b/lib/matplotlib/dviread.py @@ -121,7 +121,7 @@ def glyph_name_or_index(self): # 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() + entry = PsfontsMap(find_tex_file("pdftex.map"))[self.font.texname] return (_parse_enc(entry.encoding)[self.glyph] if entry.encoding is not None else self.glyph) diff --git a/lib/matplotlib/font_manager.pyi b/lib/matplotlib/font_manager.pyi index b5c131d33702..4bb1a8bae2a9 100644 --- a/lib/matplotlib/font_manager.pyi +++ b/lib/matplotlib/font_manager.pyi @@ -133,11 +133,19 @@ class FontManager: self, prop: str | FontProperties, fontext: Literal["ttf", "afm"] = ..., - directory: str | None = ..., + directory: str | os.PathLike | None = ..., fallback_to_default: bool = ..., rebuild_if_missing: bool = ..., ) -> FontPath: ... def get_font_names(self) -> list[str]: ... + def _find_fonts_by_props( + self, + prop: str | FontProperties, + fontext: Literal["ttf", "afm"] = ..., + directory: str | os.PathLike | None = ..., + fallback_to_default: bool = ..., + rebuild_if_missing: bool = ..., + ) -> list[FontPath]: ... def is_opentype_cff_font(filename: str) -> bool: ... def get_font( @@ -149,8 +157,8 @@ fontManager: FontManager def findfont( prop: str | FontProperties, fontext: Literal["ttf", "afm"] = ..., - directory: str | None = ..., + directory: str | os.PathLike | None = ..., fallback_to_default: bool = ..., rebuild_if_missing: bool = ..., -) -> str: ... +) -> FontPath: ... def get_font_names() -> list[str]: ... diff --git a/lib/matplotlib/ft2font.pyi b/lib/matplotlib/ft2font.pyi index 05f987292ffc..f8057742b376 100644 --- a/lib/matplotlib/ft2font.pyi +++ b/lib/matplotlib/ft2font.pyi @@ -239,7 +239,8 @@ class FT2Font(Buffer): *, face_index: int = ..., _fallback_list: list[FT2Font] | None = ..., - _kerning_factor: int | None = ... + _kerning_factor: int | None = ..., + _warn_if_used: bool = ..., ) -> None: ... if sys.version_info[:2] >= (3, 12): def __buffer__(self, /, flags: int) -> memoryview: ... diff --git a/lib/matplotlib/hatch.py b/lib/matplotlib/hatch.py index 5e0b6d761a98..4feb90d34715 100644 --- a/lib/matplotlib/hatch.py +++ b/lib/matplotlib/hatch.py @@ -206,7 +206,7 @@ def _validate_hatch_pattern(hatch): invalids = ''.join(sorted(invalids)) _api.warn_deprecated( '3.4', - removal='3.11', # one release after custom hatches (#20690) + removal='3.13', # one release after custom hatches (#20690) message=f'hatch must consist of a string of "{valid}" or ' 'None, but found the following invalid values ' f'"{invalids}". Passing invalid values is deprecated ' diff --git a/lib/matplotlib/lines.py b/lib/matplotlib/lines.py index 69ad36fb768b..c251a479151c 100644 --- a/lib/matplotlib/lines.py +++ b/lib/matplotlib/lines.py @@ -36,7 +36,7 @@ def _get_dash_pattern(style): if isinstance(style, str): style = ls_mapper.get(style, style) # un-dashed styles - if style in ['solid', 'None']: + if style in ['solid', 'None', 'none', '', ' ']: offset = 0 dashes = None # dashed styles @@ -592,9 +592,10 @@ def set_markevery(self, every): ----- Setting *markevery* will still only draw markers at actual data points. While the float argument form aims for uniform visual spacing, it has - to coerce from the ideal spacing to the nearest available data point. - Depending on the number and distribution of data points, the result - may still not look evenly spaced. + to coerce from the ideal spacing along the drawn line to the nearest + available data point. Depending on the number and distribution of data + points, and on how jagged the line is, the result may still not look + evenly spaced along the x- or y-axis. When using a start offset to specify the first marker, the offset will be from the first data point which may be different from the first diff --git a/lib/matplotlib/projections/polar.py b/lib/matplotlib/projections/polar.py index 75e1295f77f1..6908e9b45b65 100644 --- a/lib/matplotlib/projections/polar.py +++ b/lib/matplotlib/projections/polar.py @@ -34,7 +34,9 @@ class PolarTransform(mtransforms.Transform): input_dims = output_dims = 2 - def __init__(self, axis=None, use_rmin=True, *, scale_transform=None): + @_api.delete_parameter('3.11', 'apply_theta_transforms') + def __init__(self, axis=None, use_rmin=True, *, + apply_theta_transforms=False, scale_transform=None): """ Parameters ---------- @@ -183,7 +185,9 @@ class InvertedPolarTransform(mtransforms.Transform): """ input_dims = output_dims = 2 - def __init__(self, axis=None, use_rmin=True): + @_api.delete_parameter('3.11', 'apply_theta_transforms') + def __init__(self, axis=None, use_rmin=True, + *, apply_theta_transforms=False): """ Parameters ---------- @@ -1065,6 +1069,21 @@ def set_thetalim(self, *args, **kwargs): raise ValueError("The angle range must be less than a full circle") return tuple(np.rad2deg((new_min, new_max))) + def get_thetalim(self): + """ + Get the minimum and maximum theta values. + + Returns + ------- + thetamin, thetamax : float + The minimum and maximum theta limit values in degrees. + + See Also + -------- + set_thetalim + """ + return tuple(np.rad2deg(self.get_xlim())) + def set_theta_offset(self, offset): """ Set the offset for the location of 0 in radians. @@ -1224,6 +1243,21 @@ def set_rlim(self, bottom=None, top=None, *, return self.set_ylim(bottom=bottom, top=top, emit=emit, auto=auto, **kwargs) + def get_rlim(self): + """ + Get the radial axis view limits. + + Returns + ------- + bottom, top : float + The lower and upper radial axis limits. + + See Also + -------- + set_rlim + """ + return self.get_ylim() + def get_rlabel_position(self): """ Returns diff --git a/lib/matplotlib/projections/polar.pyi b/lib/matplotlib/projections/polar.pyi index fc1d508579b5..b3f18587c237 100644 --- a/lib/matplotlib/projections/polar.pyi +++ b/lib/matplotlib/projections/polar.pyi @@ -18,6 +18,7 @@ class PolarTransform(mtransforms.Transform): axis: PolarAxes | None = ..., use_rmin: bool = ..., *, + apply_theta_transforms: bool = ..., scale_transform: mtransforms.Transform | None = ..., ) -> None: ... def inverted(self) -> InvertedPolarTransform: ... @@ -34,6 +35,8 @@ class InvertedPolarTransform(mtransforms.Transform): self, axis: PolarAxes | None = ..., use_rmin: bool = ..., + *, + apply_theta_transforms: bool = ..., ) -> None: ... def inverted(self) -> PolarTransform: ... @@ -138,6 +141,7 @@ class PolarAxes(Axes): def set_thetalim(self, minval: float, maxval: float, /) -> tuple[float, float]: ... @overload def set_thetalim(self, *, thetamin: float, thetamax: float) -> tuple[float, float]: ... + def get_thetalim(self) -> tuple[float, float]: ... def set_theta_offset(self, offset: float) -> None: ... def get_theta_offset(self) -> float: ... def set_theta_zero_location( @@ -166,6 +170,7 @@ class PolarAxes(Axes): auto: bool = ..., **kwargs, ) -> tuple[float, float]: ... + def get_rlim(self) -> tuple[float, float]: ... def get_rlabel_position(self) -> float: ... def set_rlabel_position(self, value: float) -> None: ... def set_rscale(self, *args, **kwargs) -> None: ... diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index dd80da45e332..c94045621b64 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -60,6 +60,7 @@ import matplotlib import matplotlib.image from matplotlib import _api +from matplotlib._api import UNSET as _UNSET # Re-exported (import x as x) for typing. from matplotlib import get_backend as get_backend, rcParams as rcParams from matplotlib import cm as cm # noqa: F401 @@ -153,6 +154,7 @@ LogLevel ) from matplotlib.widgets import SubplotTool + from matplotlib._api import _Unset _P = ParamSpec('_P') _R = TypeVar('_R') @@ -427,6 +429,8 @@ def switch_backend(newbackend: str) -> None: try: switch_backend(candidate) except ImportError: + _log.debug("Skipping backend candidate %r as loading failed.", + candidate, exc_info=True) continue else: rcParamsOrig['backend'] = candidate @@ -3963,13 +3967,16 @@ def phase_spectrum( @_copy_docstring_and_deprecators(Axes.pie) def pie( x: ArrayLike, + *, explode: ArrayLike | None = None, labels: Sequence[str] | None = None, colors: ColorType | Sequence[ColorType] | None = None, + wedge_labels: str | Sequence | None = None, + wedge_label_distance: float | Sequence = 0.6, autopct: str | Callable[[float], str] | None = None, pctdistance: float = 0.6, shadow: bool = False, - labeldistance: float | None = 1.1, + labeldistance: float | None | _Unset = _UNSET, startangle: float = 0, radius: float = 1, counterclock: bool = True, @@ -3978,7 +3985,6 @@ def pie( center: tuple[float, float] = (0, 0), frame: bool = False, rotatelabels: bool = False, - *, normalize: bool = True, hatch: str | Sequence[str] | None = None, data=None, @@ -3988,6 +3994,8 @@ def pie( explode=explode, labels=labels, colors=colors, + wedge_labels=wedge_labels, + wedge_label_distance=wedge_label_distance, autopct=autopct, pctdistance=pctdistance, shadow=shadow, diff --git a/lib/matplotlib/scale.py b/lib/matplotlib/scale.py index 0793bb31e566..a4cce23562d3 100644 --- a/lib/matplotlib/scale.py +++ b/lib/matplotlib/scale.py @@ -138,7 +138,7 @@ def _make_axis_parameter_optional(init_func): 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. + called with or without the *axis* parameter. For simplicity, this does not handle the case when *axis* is passed as a keyword. However, @@ -170,12 +170,16 @@ def _make_axis_parameter_optional(init_func): """ @wraps(init_func) def wrapper(self, *args, **kwargs): - if args and isinstance(args[0], mpl.axis.Axis): - return init_func(self, *args, **kwargs) + sig = inspect.signature(init_func) + try: + # Try old signature. + sig.bind(self, *args, **kwargs) + except TypeError: + # Use the new signature and pass in an unused axis=None. + init_func(self, None, *args, **kwargs) else: - # Remove 'axis' from kwargs to avoid double assignment - axis = kwargs.pop('axis', None) - return init_func(self, axis, *args, **kwargs) + # Use the old signature. + init_func(self, *args, **kwargs) return wrapper @@ -449,6 +453,11 @@ def __init__(self, axis, functions, base=10): ---------- 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 ``FuncScaleLog(functions=(forward, inverse))`` is valid. functions : (callable, callable) two-tuple of the forward and inverse functions for the scale. The forward function must be monotonic. diff --git a/lib/matplotlib/sphinxext/plot_directive.py b/lib/matplotlib/sphinxext/plot_directive.py index 7b46b3145e2b..142752f23007 100644 --- a/lib/matplotlib/sphinxext/plot_directive.py +++ b/lib/matplotlib/sphinxext/plot_directive.py @@ -162,6 +162,12 @@ The plot_srcset option is incompatible with *singlehtml* builds, and an error will be raised. +plot_skip_execution + If True, will not run any plot directives. Code, captions, etc. will all + still be rendered, but no plots will be created. + + .. versionadded:: 3.12 + Notes on how it works --------------------- @@ -323,6 +329,7 @@ def setup(app): app.add_config_value('plot_working_directory', None, True) app.add_config_value('plot_template', None, True) app.add_config_value('plot_srcset', [], True) + app.add_config_value('plot_skip_execution', False, True) app.connect('doctree-read', mark_plot_labels) app.add_css_file('plot_directive.css') app.connect('build-finished', _copy_css_file) @@ -925,16 +932,19 @@ def run(arguments, content, options, state_machine, state, lineno): # make figures try: - results = render_figures(code=code, - code_path=source_file_name, - output_dir=build_dir, - output_base=output_base, - context=keep_context, - function_name=function_name, - config=config, - context_reset=context_opt == 'reset', - close_figs=context_opt == 'close-figs', - code_includes=source_file_includes) + if config.plot_skip_execution: + results = [(code, [])] + else: + results = render_figures(code=code, + code_path=source_file_name, + output_dir=build_dir, + output_base=output_base, + context=keep_context, + function_name=function_name, + config=config, + context_reset=context_opt == 'reset', + close_figs=context_opt == 'close-figs', + code_includes=source_file_includes) errors = [] except PlotError as err: reporter = state.memo.reporter diff --git a/lib/matplotlib/spines.py b/lib/matplotlib/spines.py index 741491b3dc58..35c1879a345c 100644 --- a/lib/matplotlib/spines.py +++ b/lib/matplotlib/spines.py @@ -206,6 +206,17 @@ def _ensure_position_is_set(self): self._position = ('outward', 0.0) # in points self.set_position(self._position) + def _ensure_transform_is_set(self): + # Install the default blended transform if the spine still carries + # the placeholder from Spine.__init__. Restricted to the standard + # cartesian spines: set_position/get_spine_transform only support + # those, and other spines (polar, cartopy's GeoSpine) manage their + # own transform. + if (self.spine_type in ('left', 'right', 'top', 'bottom') + and self._position is None + and self._transform is self.axes.transData): + self.set_position(('outward', 0.0)) + def register_axis(self, axis): """ Register an axis. diff --git a/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout12.png b/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout12.png index 6f5625ae2605..b4565e4c5c18 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout12.png and b/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout12.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout17.png b/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout17.png index 7ac78631798e..1b801d37ac98 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout17.png and b/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout17.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout8.png b/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout8.png index 99ba9f294a7a..5bcd4248fe2e 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout8.png and b/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout8.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_constrainedlayout/test_submerged_with_colorbar.png b/lib/matplotlib/tests/baseline_images/test_constrainedlayout/test_submerged_with_colorbar.png new file mode 100644 index 000000000000..f53c2b8035a9 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_constrainedlayout/test_submerged_with_colorbar.png differ diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index 6751666360b1..209593aee15e 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -6628,8 +6628,23 @@ def test_pie_default(): colors = ['yellowgreen', 'gold', 'lightskyblue', 'lightcoral'] explode = (0, 0.1, 0, 0) # only "explode" the 2nd slice (i.e. 'Hogs') fig1, ax1 = plt.subplots(figsize=(8, 6)) - ax1.pie(sizes, explode=explode, labels=labels, colors=colors, - autopct='%1.1f%%', shadow=True, startangle=90) + ax1.pie(sizes, explode=explode, wedge_labels=labels, wedge_label_distance=1.1, + colors=colors, autopct='%1.1f%%', shadow=True, startangle=90) + + +@image_comparison(['pie_default.png'], style='mpl20') +def test_pie_default_legacy(): + # Same as above, but uses labels parameter. Remove after labeldistance + # parameter deprecation expires. + # The slices will be ordered and plotted counter-clockwise. + labels = 'Frogs', 'Hogs', 'Dogs', 'Logs' + sizes = [15, 30, 45, 10] + colors = ['yellowgreen', 'gold', 'lightskyblue', 'lightcoral'] + explode = (0, 0.1, 0, 0) # only "explode" the 2nd slice (i.e. 'Hogs') + fig1, ax1 = plt.subplots(figsize=(8, 6)) + with pytest.warns(mpl.MatplotlibDeprecationWarning): + ax1.pie(sizes, explode=explode, labels=labels, colors=colors, + autopct='%1.1f%%', shadow=True, startangle=90) @image_comparison(['pie_linewidth_0.png', 'pie_linewidth_0.png', 'pie_linewidth_0.png'], @@ -6641,27 +6656,30 @@ def test_pie_linewidth_0(): colors = ['yellowgreen', 'gold', 'lightskyblue', 'lightcoral'] explode = (0, 0.1, 0, 0) # only "explode" the 2nd slice (i.e. 'Hogs') - plt.pie(sizes, explode=explode, labels=labels, colors=colors, - autopct='%1.1f%%', shadow=True, startangle=90, + plt.pie(sizes, explode=explode, wedge_labels=labels, wedge_label_distance=1.1, + colors=colors, autopct='%1.1f%%', shadow=True, startangle=90, wedgeprops={'linewidth': 0}) # Set aspect ratio to be equal so that pie is drawn as a circle. plt.axis('equal') - # Reuse testcase from above for a labeled data test + # Reuse testcase from above for a labeled data test. Include legend labels + # to smoke test that they are correctly unpacked. data = {"l": labels, "s": sizes, "c": colors, "ex": explode} fig = plt.figure() ax = fig.gca() - ax.pie("s", explode="ex", labels="l", colors="c", + ax.pie("s", explode="ex", wedge_labels="l", colors="c", wedge_label_distance=1.1, autopct='%1.1f%%', shadow=True, startangle=90, - wedgeprops={'linewidth': 0}, data=data) + labels="l", labeldistance=None, wedgeprops={'linewidth': 0}, + data=data) ax.axis('equal') # And again to test the pyplot functions which should also be able to be # called with a data kwarg plt.figure() - plt.pie("s", explode="ex", labels="l", colors="c", + plt.pie("s", explode="ex", wedge_labels="l", colors="c", wedge_label_distance=1.1, autopct='%1.1f%%', shadow=True, startangle=90, - wedgeprops={'linewidth': 0}, data=data) + labels="l", labeldistance=None, wedgeprops={'linewidth': 0}, + data=data) plt.axis('equal') @@ -6674,8 +6692,8 @@ def test_pie_center_radius(): colors = ['yellowgreen', 'gold', 'lightskyblue', 'lightcoral'] explode = (0, 0.1, 0, 0) # only "explode" the 2nd slice (i.e. 'Hogs') - plt.pie(sizes, explode=explode, labels=labels, colors=colors, - autopct='%1.1f%%', shadow=True, startangle=90, + plt.pie(sizes, explode=explode, wedge_labels=labels, wedge_label_distance=1.1, + colors=colors, autopct='%1.1f%%', shadow=True, startangle=90, wedgeprops={'linewidth': 0}, center=(1, 2), radius=1.5) plt.annotate("Center point", xy=(1, 2), xytext=(1, 1.3), @@ -6694,8 +6712,8 @@ def test_pie_linewidth_2(): colors = ['yellowgreen', 'gold', 'lightskyblue', 'lightcoral'] explode = (0, 0.1, 0, 0) # only "explode" the 2nd slice (i.e. 'Hogs') - plt.pie(sizes, explode=explode, labels=labels, colors=colors, - autopct='%1.1f%%', shadow=True, startangle=90, + plt.pie(sizes, explode=explode, wedge_labels=labels, wedge_label_distance=1.1, + colors=colors, autopct='%1.1f%%', shadow=True, startangle=90, wedgeprops={'linewidth': 2}) # Set aspect ratio to be equal so that pie is drawn as a circle. plt.axis('equal') @@ -6709,8 +6727,8 @@ def test_pie_ccw_true(): colors = ['yellowgreen', 'gold', 'lightskyblue', 'lightcoral'] explode = (0, 0.1, 0, 0) # only "explode" the 2nd slice (i.e. 'Hogs') - plt.pie(sizes, explode=explode, labels=labels, colors=colors, - autopct='%1.1f%%', shadow=True, startangle=90, + plt.pie(sizes, explode=explode, wedge_labels=labels, wedge_label_distance=1.1, + colors=colors, autopct='%1.1f%%', shadow=True, startangle=90, counterclock=True) # Set aspect ratio to be equal so that pie is drawn as a circle. plt.axis('equal') @@ -6725,35 +6743,53 @@ def test_pie_frame_grid(): # only "explode" the 2nd slice (i.e. 'Hogs') explode = (0, 0.1, 0, 0) - plt.pie(sizes, explode=explode, labels=labels, colors=colors, - autopct='%1.1f%%', shadow=True, startangle=90, + plt.pie(sizes, explode=explode, wedge_labels=labels, wedge_label_distance=1.1, + colors=colors, autopct='%1.1f%%', shadow=True, startangle=90, wedgeprops={'linewidth': 0}, frame=True, center=(2, 2)) - plt.pie(sizes[::-1], explode=explode, labels=labels, colors=colors, - autopct='%1.1f%%', shadow=True, startangle=90, + plt.pie(sizes[::-1], explode=explode, wedge_labels=labels, wedge_label_distance=1.1, + colors=colors, autopct='%1.1f%%', shadow=True, startangle=90, wedgeprops={'linewidth': 0}, frame=True, center=(5, 2)) - plt.pie(sizes, explode=explode[::-1], labels=labels, colors=colors, - autopct='%1.1f%%', shadow=True, startangle=90, + plt.pie(sizes, explode=explode[::-1], wedge_labels=labels, wedge_label_distance=1.1, + colors=colors, autopct='%1.1f%%', shadow=True, startangle=90, wedgeprops={'linewidth': 0}, frame=True, center=(3, 5)) # Set aspect ratio to be equal so that pie is drawn as a circle. plt.axis('equal') +@image_comparison(['pie_rotatelabels_true.png'], style='mpl20') +def test_pie_label_rotate(): + # The slices will be ordered and plotted counter-clockwise. + labels = 'Hogwarts', 'Frogs', 'Dogs', 'Logs' + sizes = [15, 30, 45, 10] + colors = ['yellowgreen', 'gold', 'lightskyblue', 'lightcoral'] + explode = (0, 0.1, 0, 0) # only "explode" the 2nd slice (i.e. 'Frogs') + + pie = plt.pie(sizes, explode=explode, wedge_labels='{frac:.1%}', colors=colors, + shadow=True, startangle=90) + plt.pie_label(pie, labels, distance=1.1, rotate=True) + # Set aspect ratio to be equal so that pie is drawn as a circle. + plt.axis('equal') + + @image_comparison(['pie_rotatelabels_true.png'], style='mpl20') def test_pie_rotatelabels_true(): + # As above but using legacy labels and rotatelabels parameters. Remove + # when the labeldistance parameter deprecation expires. # The slices will be ordered and plotted counter-clockwise. labels = 'Hogwarts', 'Frogs', 'Dogs', 'Logs' sizes = [15, 30, 45, 10] colors = ['yellowgreen', 'gold', 'lightskyblue', 'lightcoral'] - explode = (0, 0.1, 0, 0) # only "explode" the 2nd slice (i.e. 'Hogs') + explode = (0, 0.1, 0, 0) # only "explode" the 2nd slice (i.e. 'Frogs') - plt.pie(sizes, explode=explode, labels=labels, colors=colors, - autopct='%1.1f%%', shadow=True, startangle=90, - rotatelabels=True) + with pytest.warns(mpl.MatplotlibDeprecationWarning): + plt.pie(sizes, explode=explode, labels=labels, colors=colors, + autopct='%1.1f%%', shadow=True, startangle=90, + rotatelabels=True) # Set aspect ratio to be equal so that pie is drawn as a circle. plt.axis('equal') @@ -6765,7 +6801,7 @@ def test_pie_nolabel_but_legend(): colors = ['yellowgreen', 'gold', 'lightskyblue', 'lightcoral'] explode = (0, 0.1, 0, 0) # only "explode" the 2nd slice (i.e. 'Hogs') plt.pie(sizes, explode=explode, labels=labels, colors=colors, - autopct='%1.1f%%', shadow=True, startangle=90, labeldistance=None, + wedge_labels='{frac:.1%}', shadow=True, startangle=90, labeldistance=None, rotatelabels=True) plt.axis('equal') plt.ylim(-1.2, 1.2) @@ -6806,9 +6842,13 @@ def test_pie_textprops(): rotation_mode="anchor", size=12, color="red") - _, texts, autopct = plt.gca().pie(data, labels=labels, autopct='%.2f', - textprops=textprops) - for labels in [texts, autopct]: + fig, ax = plt.subplots() + + pie1 = ax.pie(data, wedge_labels=labels, autopct='%.2f', textprops=textprops) + with pytest.warns(mpl.MatplotlibDeprecationWarning): + pie2 = ax.pie(data, labels=labels, textprops=textprops) + + for labels in pie1.texts + pie2.texts: for tx in labels: assert tx.get_ha() == textprops["horizontalalignment"] assert tx.get_va() == textprops["verticalalignment"] @@ -6836,7 +6876,7 @@ def test_pie_invalid_labels(): # Test ValueError raised when feeding short labels list to axes.pie fig, ax = plt.subplots() with pytest.raises(ValueError): - ax.pie([1, 2, 3], labels=["One", "Two"]) + ax.pie([1, 2, 3], labels=["One", "Two"], labeldistance=None) def test_pie_invalid_radius(): @@ -6846,6 +6886,13 @@ def test_pie_invalid_radius(): ax.pie([1, 2, 3], radius=-5) +def test_pie_wedge_labels_and_labels(): + fig, ax = plt.subplots() + with pytest.raises(ValueError, match='wedge_labels is a replacement for labels'): + ax.pie([1, 2], wedge_labels=['spam', 'eggs'], labels=['bacon', 'beans'], + labeldistance=1.2) + + def test_normalize_kwarg_pie(): fig, ax = plt.subplots() x = [0.3, 0.3, 0.1] @@ -10047,21 +10094,13 @@ def assert_not_in_reference_cycle(start): def test_boxplot_tick_labels(): - # Test the renamed `tick_labels` parameter. - # Test for deprecation of old name `labels`. + # Test the `tick_labels` parameter. np.random.seed(19680801) data = np.random.random((10, 3)) - fig, axs = plt.subplots(nrows=1, ncols=2, sharey=True) - # Should get deprecation warning for `labels` - with pytest.warns(mpl.MatplotlibDeprecationWarning, - match='has been renamed \'tick_labels\''): - axs[0].boxplot(data, labels=['A', 'B', 'C']) - assert [l.get_text() for l in axs[0].get_xticklabels()] == ['A', 'B', 'C'] - - # Test the new tick_labels parameter - axs[1].boxplot(data, tick_labels=['A', 'B', 'C']) - assert [l.get_text() for l in axs[1].get_xticklabels()] == ['A', 'B', 'C'] + fig, ax = plt.subplots() + ax.boxplot(data, tick_labels=['A', 'B', 'C']) + assert [l.get_text() for l in ax.get_xticklabels()] == ['A', 'B', 'C'] @needs_usetex @@ -10323,13 +10362,13 @@ def test_pie_non_finite_values(): df = [5, float('nan'), float('inf')] with pytest.raises(ValueError, match='Wedge sizes must be finite numbers'): - ax.pie(df, labels=['A', 'B', 'C']) + ax.pie(df) def test_pie_all_zeros(): fig, ax = plt.subplots() with pytest.raises(ValueError, match="All wedge sizes are zero"): - ax.pie([0, 0], labels=["A", "B"]) + ax.pie([0, 0]) def test_animated_artists_not_drawn_by_default(): diff --git a/lib/matplotlib/tests/test_backend_pgf.py b/lib/matplotlib/tests/test_backend_pgf.py index e5b73c9450f3..4af329fa28d4 100644 --- a/lib/matplotlib/tests/test_backend_pgf.py +++ b/lib/matplotlib/tests/test_backend_pgf.py @@ -15,7 +15,7 @@ from matplotlib.testing import _has_tex_package, _check_for_pgf from matplotlib.testing.exceptions import ImageComparisonFailure from matplotlib.testing.compare import compare_images -from matplotlib.backends.backend_pgf import PdfPages +from matplotlib.backends.backend_pgf import _metadata_to_str, PdfPages from matplotlib.testing.decorators import ( _image_directories, check_figures_equal, image_comparison) from matplotlib.testing._markers import ( @@ -37,6 +37,26 @@ def compare_figure(fname, savefig_kwargs={}, tol=0): raise ImageComparisonFailure(err) +@pytest.mark.parametrize("key, value, expected_str", [ + ("Author", "me", "Author={me}"), + ("ModDate", + datetime.datetime(1968, 8, 1, tzinfo=datetime.timezone(datetime.timedelta(0))), + "ModDate={D:19680801000000Z}"), +]) +def test__metadata_to_str(key, value, expected_str): + assert _metadata_to_str(key, value) == expected_str + + +@pytest.mark.parametrize("value", [ + r"Backslashes, e.g. in \commands", + r"funny braces {}", + r"and square brackets]", +]) +def test__metadata_to_str_error(value): + with pytest.raises(ValueError, match="value must not contain the chars"): + _metadata_to_str("Title", value) + + @needs_pgf_xelatex @needs_ghostscript @pytest.mark.backend('pgf') diff --git a/lib/matplotlib/tests/test_backend_svg.py b/lib/matplotlib/tests/test_backend_svg.py index ba565eadb01b..6b63990f7620 100644 --- a/lib/matplotlib/tests/test_backend_svg.py +++ b/lib/matplotlib/tests/test_backend_svg.py @@ -65,7 +65,7 @@ def test_text_urls(): fig.savefig(fd, format='svg') buf = fd.getvalue().decode() - expected = f'' + expected = f'' assert expected in buf diff --git a/lib/matplotlib/tests/test_collections.py b/lib/matplotlib/tests/test_collections.py index 9c9b4e643014..46122b8b1e6a 100644 --- a/lib/matplotlib/tests/test_collections.py +++ b/lib/matplotlib/tests/test_collections.py @@ -704,6 +704,17 @@ def test_set_wrong_linestyle(): c.set_linestyle('fuzzy') +@pytest.mark.parametrize('ls', ['', ' ', 'none']) +def test_scatter_empty_linestyle_pdf(ls): + # Regression test: '', ' ', and 'none' are documented "draw nothing" + # linestyle aliases but were not recognized by _get_dash_pattern, causing + # savefig to PDF to crash with "zero-size array to reduction operation maximum". + plt.switch_backend('pdf') + fig, ax = plt.subplots() + ax.scatter([0, 1], [0, 1], ls=ls) + fig.savefig(io.BytesIO()) + + @mpl.style.context('default') def test_capstyle(): col = mcollections.PathCollection([]) diff --git a/lib/matplotlib/tests/test_constrainedlayout.py b/lib/matplotlib/tests/test_constrainedlayout.py index ff757c1ce9fc..7d1314ed5042 100644 --- a/lib/matplotlib/tests/test_constrainedlayout.py +++ b/lib/matplotlib/tests/test_constrainedlayout.py @@ -809,3 +809,47 @@ def test_submerged_subfig(): for ax in axs[1:]: assert np.allclose(ax.get_position().bounds[-1], axs[0].get_position().bounds[-1], atol=1e-6) + + +def test_submerged_height_gap(): + """Test that the gap between rows does not depend on the number of columns.""" + + mosaic1 = "AC;BC" + mosaic2 = "ACDE;BCDE" + + fig1, ax_dict1 = plt.subplot_mosaic(mosaic1, layout='constrained') + fig2, ax_dict2 = plt.subplot_mosaic(mosaic2, layout='constrained') + for fig in fig1, fig2: + fig.get_layout_engine().set(h_pad=0.2) + fig.draw_without_rendering() + + for label in 'A', 'B': + np.testing.assert_allclose(ax_dict1[label].get_position().bounds[-1], + ax_dict2[label].get_position().bounds[-1]) + + +def test_submerged_width_gap(): + """Test that the gap between columns does not depend on the number of rows.""" + + mosaic1 = "AB;CC" + mosaic2 = "AB;CC;DD" + + fig1, ax_dict1 = plt.subplot_mosaic(mosaic1, layout='constrained') + fig2, ax_dict2 = plt.subplot_mosaic(mosaic2, layout='constrained') + for fig in fig1, fig2: + fig.get_layout_engine().set(w_pad=0.2) + fig.draw_without_rendering() + + for label in 'A', 'B': + np.testing.assert_allclose(ax_dict1[label].get_position().bounds[-2], + ax_dict2[label].get_position().bounds[-2]) + + +@image_comparison(['test_submerged_with_colorbar.png'], style='mpl20') +def test_submerged_with_colorbar(): + mosaic = "AABBCC;DDDEEE" + + fig, ax_dict = plt.subplot_mosaic(mosaic, layout='constrained') + + cf = ax_dict['A'].contourf([[0, 1], [2, 3]]) + fig.colorbar(cf) diff --git a/lib/matplotlib/tests/test_container.py b/lib/matplotlib/tests/test_container.py index b7dfe1196685..d27ee1115171 100644 --- a/lib/matplotlib/tests/test_container.py +++ b/lib/matplotlib/tests/test_container.py @@ -57,10 +57,13 @@ def test_barcontainer_position_centers__bottoms__tops(): def test_piecontainer_remove(): fig, ax = plt.subplots() - pie = ax.pie([2, 3], labels=['foo', 'bar'], autopct="%1.0f%%") + pie = ax.pie([2, 3], wedge_labels=['foo', 'bar'], autopct="%1.0f%%") ax.pie_label(pie, ['baz', 'qux']) + assert len(ax.patches) == 2 - assert len(ax.texts) == 6 + # We have added 6 labels but pie also adds an empty Text artist to each + # wedge if labeldistance is not None and labels is not passed + assert len(ax.texts) == 8 pie.remove() assert not ax.patches diff --git a/lib/matplotlib/tests/test_polar.py b/lib/matplotlib/tests/test_polar.py index a805fb61d238..3fbf9aea16d5 100644 --- a/lib/matplotlib/tests/test_polar.py +++ b/lib/matplotlib/tests/test_polar.py @@ -398,6 +398,29 @@ def test_axvspan(): assert span.get_path()._interpolation_steps > 1 +def test_polar_get_rlim(): + # PolarAxes.get_rlim() should mirror set_rlim() + ax = plt.figure().add_subplot(projection='polar') + ax.set_rlim(1.5, 8.0) + assert ax.get_rlim() == (1.5, 8.0) + + +def test_polar_get_rlim_after_plot(): + # get_rlim() should work after autoscaling via plot() + ax = plt.figure().add_subplot(projection='polar') + theta = np.linspace(0, 2 * np.pi, 10) + ax.plot(theta, np.ones(10) * 5.0) + rmin, rmax = ax.get_rlim() + assert rmax >= 5.0 + + +def test_polar_get_thetalim(): + # PolarAxes.get_thetalim() should mirror set_thetalim() + ax = plt.figure().add_subplot(projection='polar') + ax.set_thetalim(thetamin=30, thetamax=90) + assert_allclose(ax.get_thetalim(), (30, 90)) + + @check_figures_equal() def test_remove_shared_polar(fig_ref, fig_test): # Removing shared polar axes used to crash. Test removing them, keeping in @@ -591,3 +614,13 @@ def test_radial_locator_wrapping(): assert ax.yaxis.isDefault_majloc assert isinstance(ax.yaxis.get_major_locator(), RadialLocator) assert isinstance(ax.yaxis.get_major_locator().base, mticker.LogLocator) + + +def test_set_rticks_ticklabels_no_warning(): + # Regression test: RadialLocator wrapping a FixedLocator must not trigger + # the "set_ticklabels() should only be used with a fixed number of ticks" + # UserWarning when set_ticks()/set_rticks() was called first. + + fig, ax = plt.subplots(subplot_kw={'projection': 'polar'}) + ax.set_rticks([0, 1, 2, 3]) + ax.yaxis.set_ticklabels(['zero', 'one', 'two', 'three']) diff --git a/lib/matplotlib/tests/test_scale.py b/lib/matplotlib/tests/test_scale.py index 95601ce97b65..104c87adab7b 100644 --- a/lib/matplotlib/tests/test_scale.py +++ b/lib/matplotlib/tests/test_scale.py @@ -3,6 +3,7 @@ import matplotlib.pyplot as plt from matplotlib.scale import ( AsinhScale, AsinhTransform, + FuncScale, LogitScale, LogTransform, InvertedLogTransform, SymmetricalLogTransform) import matplotlib.scale as mscale @@ -19,6 +20,49 @@ import pytest +def test_optional_axis_signature(): + # There are three types of original signatures possible, and this only tests one + # example class of each: + # 1. `axis` without default: LinearScale, FuncScale, FuncScaleLog + # 2. `axis` with default and more positional parameters: LogitScale + # 3. `axis` with default and only keyword-only parameters: LogScale, AsinhScale, + # SymmetricalLogScale + # Testing with None is sufficient as detection is purely based on the + # signature structure; no type information is involved. + axis = None + + # Old signature with axis positionally. + FuncScale(axis, (lambda x: x, lambda x: x)) + FuncScale(axis, functions=(lambda x: x, lambda x: x)) + LogitScale(axis) + LogitScale(axis, 'clip') + LogitScale(axis, nonpositive='clip') + LogitScale(axis, use_overline=True) + AsinhScale(axis) + AsinhScale(axis, linear_width=2) + AsinhScale(axis, base=3) + AsinhScale(axis, subs=[2, 6]) + # Old signature with axis as keyword. + FuncScale(axis=axis, functions=(lambda x: x, lambda x: x)) + LogitScale(axis=axis) + LogitScale(axis=axis, nonpositive='clip') + LogitScale(axis=axis, use_overline=True) + AsinhScale(axis=axis) + AsinhScale(axis=axis, linear_width=2) + AsinhScale(axis=axis, base=3) + AsinhScale(axis=axis, subs=[2, 6]) + # New signature without axis. + FuncScale((lambda x: x, lambda x: x)) + FuncScale(functions=(lambda x: x, lambda x: x)) + LogitScale() + LogitScale(nonpositive='clip') + LogitScale(use_overline=True) + AsinhScale() + AsinhScale(linear_width=2) + AsinhScale(base=3) + AsinhScale(subs=[2, 6]) + + @check_figures_equal() def test_log_scales(fig_test, fig_ref): ax_test = fig_test.add_subplot(122, yscale='log', xscale='symlog') diff --git a/lib/matplotlib/tests/test_sphinxext.py b/lib/matplotlib/tests/test_sphinxext.py index c6f4e13c74c2..5b0eacd50e40 100644 --- a/lib/matplotlib/tests/test_sphinxext.py +++ b/lib/matplotlib/tests/test_sphinxext.py @@ -269,3 +269,51 @@ def plot_file(num, suff=''): st = ('srcset="../_images/nestedpage2-index-2.png, ' '../_images/nestedpage2-index-2.2x.png 2.00x"') assert st in (html_dir / 'nestedpage2/index.html').read_text(encoding='utf-8') + + +def test_plot_skip_execution(tmp_path): + # test that modifying plot_exclude_patterns in config leads to skipping files + shutil.copyfile(tinypages / 'conf.py', tmp_path / 'conf.py') + shutil.copytree(tinypages / '_static', tmp_path / '_static') + shutil.copyfile(tinypages / 'range4.py', tmp_path / 'range4.py') + shutil.copyfile(tinypages / 'range6.py', tmp_path / 'range6.py') + + html_dir = tmp_path / '_build' / 'html' + img_dir = html_dir / '_images' + doctree_dir = tmp_path / 'doctrees' + + (tmp_path / 'index.rst').write_text(""" +.. plot:: + + plt.plot(range(2)) + +.. toctree:: + + script_func + script_nofunc +""") + (tmp_path / 'script_func.rst').write_text(""" +########## +Some plots +########## + +.. plot:: range6.py range6 + +.. plot:: range6.py range10 +""") + (tmp_path / 'script_nofunc.rst').write_text(""" +########## +Some plots +########## + +.. plot:: range4.py +""") + + # Build the pages with warnings turned into errors + build_sphinx_html(tmp_path, doctree_dir, html_dir, + extra_args=["-D", "plot_skip_execution=1"]) + + assert not (img_dir / "index-1.png").exists() + assert not (img_dir / "range6_range6.png").exists() + assert not (img_dir / "range6_range10.png").exists() + assert not (img_dir / "range4.png").exists() diff --git a/lib/matplotlib/tests/test_spines.py b/lib/matplotlib/tests/test_spines.py index b652b1f78867..1f0122f27aff 100644 --- a/lib/matplotlib/tests/test_spines.py +++ b/lib/matplotlib/tests/test_spines.py @@ -1,8 +1,9 @@ import numpy as np import pytest +import matplotlib.path as mpath import matplotlib.pyplot as plt -from matplotlib.spines import Spines +from matplotlib.spines import Spine, Spines from matplotlib.testing.decorators import check_figures_equal, image_comparison @@ -197,3 +198,19 @@ def test_spine_set_bounds_with_none(): "left bound should be numeric" assert np.isclose(left_bound[0], ylim[0]), "Lower bound should match original value" assert np.isclose(left_bound[1], ylim[1]), "Upper bound should match original value" + + +def test_clear_with_custom_spine_type(): + # Spines with a non-cartesian spine_type (e.g. cartopy's GeoSpine) manage + # their own transform and may reject set_position(); Axes.clear() must not + # call _ensure_transform_is_set() on them. See SciTools/cartopy#2674. + class NoPositionSpine(Spine): + def __init__(self, axes, **kwargs): + super().__init__(axes, 'geo', mpath.Path(np.empty((0, 2))), **kwargs) + + def set_position(self, position): + raise NotImplementedError('spine does not support set_position') + + fig, ax = plt.subplots() + ax.spines['geo'] = NoPositionSpine(ax) + ax.clear() # must not raise diff --git a/lib/mpl_toolkits/axisartist/axis_artist.py b/lib/mpl_toolkits/axisartist/axis_artist.py index 3ba70c3d7d3b..75dd978eb6f9 100644 --- a/lib/mpl_toolkits/axisartist/axis_artist.py +++ b/lib/mpl_toolkits/axisartist/axis_artist.py @@ -1086,7 +1086,7 @@ def get_tightbbox(self, renderer=None): *self.minor_ticklabels.get_window_extents(renderer), self.label.get_window_extent(renderer), self.offsetText.get_window_extent(renderer), - self.line.get_window_extent(renderer), + self.line.get_tightbbox(renderer), ] bb = [b for b in bb if b and (b.width != 0 or b.height != 0)] if bb: diff --git a/lib/mpl_toolkits/axisartist/tests/test_axis_artist.py b/lib/mpl_toolkits/axisartist/tests/test_axis_artist.py index 8c67b18c0349..9145ce666f32 100644 --- a/lib/mpl_toolkits/axisartist/tests/test_axis_artist.py +++ b/lib/mpl_toolkits/axisartist/tests/test_axis_artist.py @@ -1,7 +1,13 @@ +import numpy as np + +import matplotlib as mpl import matplotlib.pyplot as plt +from matplotlib.projections import PolarAxes from matplotlib.testing.decorators import image_comparison +from matplotlib.transforms import Affine2D -from mpl_toolkits.axisartist import AxisArtistHelperRectlinear +from mpl_toolkits.axisartist import (AxisArtistHelperRectlinear, GridHelperCurveLinear, + HostAxes) from mpl_toolkits.axisartist.axis_artist import (AxisArtist, AxisLabel, LabelBase, Ticks, TickLabels) @@ -90,3 +96,24 @@ def test_axis_artist(): axisline.label.set_pad(5) ax.set_ylabel("Test") + + +@mpl.style.context('default') +def test_axisartist_tightbbox(): + fig = plt.figure() + tr = Affine2D().scale(np.pi / 180., 1.) + PolarAxes.PolarTransform() + grid_helper = GridHelperCurveLinear(tr) + ax = fig.add_subplot(axes_class=HostAxes, grid_helper=grid_helper) + ax.axis["lon"] = ax.new_floating_axis(1, 9) + + ax.set_xlim(-5, 12) + ax.set_ylim(-5, 10) + + ax.axis['lon'].major_ticklabels.set_visible(False) + + # Since the labels are invisible and the lines are clipped to the axes, + # the axis's tight bbox should be contained in the axes box. + renderer = fig._get_renderer() + tight_points = ax.axis['lon'].get_tightbbox(renderer).get_points() + for point in tight_points: + assert ax.bbox.contains(*point) diff --git a/lib/mpl_toolkits/mplot3d/art3d.py b/lib/mpl_toolkits/mplot3d/art3d.py index 6898a8aaf4cf..f664127dcb59 100644 --- a/lib/mpl_toolkits/mplot3d/art3d.py +++ b/lib/mpl_toolkits/mplot3d/art3d.py @@ -819,18 +819,26 @@ def do_3d_projection(self): return np.nan def _maybe_depth_shade_and_sort_colors(self, color_array): - color_array = ( - _zalpha( + # Adjust the color_array alpha values if point depths are defined + # and depth shading is active + alpha = self._alpha + if self._vzs is not None and self._depthshade: + color_array = _zalpha( color_array, self._vzs, min_alpha=self._depthshade_minalpha, ) - if self._vzs is not None and self._depthshade - else color_array - ) + if alpha is not None and color_array.shape[1] == 4: # RGBA, not RGB + alpha = alpha * color_array[:, 3] + + # Adjust the order of the color_array using the _z_markers_idx, + # which has been sorted by z-depth if len(color_array) > 1: color_array = color_array[self._z_markers_idx] - return mcolors.to_rgba_array(color_array, self._alpha) + if np.ndim(alpha) > 0: + alpha = np.asarray(alpha)[self._z_markers_idx] + + return mcolors.to_rgba_array(color_array, alpha) def get_facecolor(self): return self._maybe_depth_shade_and_sort_colors(super().get_facecolor()) @@ -1070,6 +1078,7 @@ def _use_zordered_offset(self): def _maybe_depth_shade_and_sort_colors(self, color_array): # Adjust the color_array alpha values if point depths are defined # and depth shading is active + alpha = self._alpha if self._vzs is not None and self._depthshade: color_array = _zalpha( color_array, @@ -1077,13 +1086,17 @@ def _maybe_depth_shade_and_sort_colors(self, color_array): min_alpha=self._depthshade_minalpha, _data_scale=self._data_scale, ) + if alpha is not None and color_array.shape[1] == 4: # RGBA, not RGB + alpha = alpha * color_array[:, 3] # Adjust the order of the color_array using the _z_markers_idx, # which has been sorted by z-depth if len(color_array) > 1: color_array = color_array[self._z_markers_idx] + if np.ndim(alpha) > 0: + alpha = np.asarray(alpha)[self._z_markers_idx] - return mcolors.to_rgba_array(color_array) + return mcolors.to_rgba_array(color_array, alpha) def get_facecolor(self): return self._maybe_depth_shade_and_sort_colors(super().get_facecolor()) diff --git a/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py b/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py index 078da596a9a4..2a5593a641c9 100644 --- a/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py +++ b/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py @@ -440,6 +440,19 @@ def test_scatter3d_linewidth(): marker='o', linewidth=np.arange(10)) +@check_figures_equal() +def test_scatter3d_cmap_alpha(fig_ref, fig_test): + # Check that alpha is applied correctly with colormapped scatter. + # Regression test for https://github.com/matplotlib/matplotlib/issues/25468 + x, y, z = np.arange(5), np.zeros(5), np.arange(5) + c = np.array([0, 1, np.nan, 3, 4]) + + ax_test = fig_test.add_subplot(projection='3d') + ax_test.scatter(x, y, z, c=c) + ax_ref = fig_ref.add_subplot(projection='3d') + ax_ref.scatter(x, y, z, c=c, alpha=1) + + @check_figures_equal() def test_scatter3d_linewidth_modification(fig_ref, fig_test): # Changing Path3DCollection linewidths with array-like post-creation diff --git a/meson.build b/meson.build index 7d1f3a433fbb..24a09821a047 100644 --- a/meson.build +++ b/meson.build @@ -6,7 +6,6 @@ project( find_program('python3', 'python', version: '>= 3.11'), '-m', 'setuptools_scm', check: true).stdout().strip(), # qt_editor backend is MIT - # ResizeObserver at end of lib/matplotlib/backends/web_backend/js/mpl.js is CC0 # STIX, Computer Modern, and Last Resort are OFL # DejaVu is Bitstream Vera and Public Domain license: 'PSF-2.0 AND MIT AND CC0-1.0 AND OFL-1.1 AND Bitstream-Vera AND Public-Domain', @@ -19,7 +18,6 @@ project( 'LICENSE/LICENSE_COURIERTEN', 'LICENSE/LICENSE_FREETYPE', 'LICENSE/LICENSE_HARFBUZZ', - 'LICENSE/LICENSE_JSXTOOLS_RESIZE_OBSERVER', 'LICENSE/LICENSE_LAST_RESORT_FONT', 'LICENSE/LICENSE_LIBRAQM', 'LICENSE/LICENSE_QT4_EDITOR', diff --git a/pyproject.toml b/pyproject.toml index f3c38512a2c9..eef7f82fb810 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -61,6 +61,8 @@ requires = [ # you really need it and aren't using an sdist. "meson-python>=0.13.2,!=0.17.*", "pybind11>=2.13.2,!=2.13.3", + # setuptools_scm 10 breaks versioning in editable installs. You can remove this pin + # if you're a downstream distributor just building wheels or your equivalent. "setuptools_scm>=7,<10", ] diff --git a/src/_image_resample.h b/src/_image_resample.h index eaaf2306ae9f..2c48a080a66d 100644 --- a/src/_image_resample.h +++ b/src/_image_resample.h @@ -693,6 +693,20 @@ static void get_filter(const resample_params_t ¶ms, } +template +void render_image(renderer_t &renderer, rasterizer_t &rasterizer, span_gen_t &span_gen, double alpha) +{ + auto span_alloc = agg::span_allocator{}; + auto conv_alpha = span_conv_alpha{alpha}; + auto span_conv = agg::span_converter{span_gen, conv_alpha}; + + auto renderer_scanline = agg::renderer_scanline_aa{renderer, span_alloc, span_conv}; + + auto scanline = agg::scanline32_u8{}; + agg::render_scanlines(rasterizer, scanline, renderer_scanline); +} + + template void resample( const void *input, int in_width, int in_height, @@ -704,19 +718,7 @@ void resample( using input_pixfmt_t = typename type_mapping_t::pixfmt_type; using output_pixfmt_t = typename type_mapping_t::pixfmt_type; - using renderer_t = agg::renderer_base; - using rasterizer_t = agg::rasterizer_scanline_aa; - using scanline_t = agg::scanline32_u8; - - using reflect_t = agg::wrap_mode_reflect; - using image_accessor_wrap_t = agg::image_accessor_wrap; - using image_accessor_clip_t = agg::image_accessor_clip; - - using span_alloc_t = agg::span_allocator; - using span_conv_alpha_t = span_conv_alpha; - - using nn_affine_interpolator_t = accurate_interpolator_affine_nn<>; - using affine_interpolator_t = agg::span_interpolator_linear<>; + // Need to define this class explicitly because the first argument cannot be deduced using arbitrary_interpolator_t = agg::span_interpolator_adaptor, lookup_distortion>; @@ -734,28 +736,24 @@ void resample( params.interpolation = NEAREST; } - span_alloc_t span_alloc; - rasterizer_t rasterizer; - scanline_t scanline; - - span_conv_alpha_t conv_alpha(params.alpha); - agg::rendering_buffer input_buffer; input_buffer.attach( (unsigned char *)input, in_width, in_height, in_width * itemsize); input_pixfmt_t input_pixfmt(input_buffer); - image_accessor_wrap_t input_accessor_wrap(input_pixfmt); - image_accessor_clip_t input_accessor_clip(input_pixfmt, color_type::no_color()); + auto image_accessor_wrap = + agg::image_accessor_wrap{input_pixfmt}; + auto image_accessor_clip = agg::image_accessor_clip{input_pixfmt, color_type::no_color()}; agg::rendering_buffer output_buffer; output_buffer.attach( (unsigned char *)output, out_width, out_height, out_width * itemsize); output_pixfmt_t output_pixfmt(output_buffer); - renderer_t renderer(output_pixfmt); + auto renderer = agg::renderer_base{output_pixfmt}; agg::trans_affine inverted = params.affine; inverted.invert(); + auto rasterizer = agg::rasterizer_scanline_aa{}; rasterizer.clip_box(0, 0, out_width, out_height); agg::path_storage path; @@ -808,50 +806,42 @@ void resample( if (params.interpolation == NEAREST) { if (params.is_affine) { - using span_gen_t = typename type_mapping_t::template span_gen_nn_type; - using span_conv_t = agg::span_converter; - using nn_renderer_t = agg::renderer_scanline_aa; - nn_affine_interpolator_t interpolator(inverted); - span_gen_t span_gen(input_accessor_clip, interpolator); - span_conv_t span_conv(span_gen, conv_alpha); - nn_renderer_t nn_renderer(renderer, span_alloc, span_conv); - agg::render_scanlines(rasterizer, scanline, nn_renderer); + auto interpolator = accurate_interpolator_affine_nn{inverted}; + // C++17 cannot deduce arguments for an alias class template, so define the class explicitly + using span_gen_t = typename type_mapping_t:: + template span_gen_nn_type; + auto span_gen = span_gen_t{image_accessor_clip, interpolator}; + render_image(renderer, rasterizer, span_gen, params.alpha); } else { - using span_gen_t = typename type_mapping_t::template span_gen_nn_type; - using span_conv_t = agg::span_converter; - using nn_renderer_t = agg::renderer_scanline_aa; lookup_distortion dist( params.transform_mesh, in_width, in_height, out_width, out_height, true); - arbitrary_interpolator_t interpolator(inverted, dist); - span_gen_t span_gen(input_accessor_clip, interpolator); - span_conv_t span_conv(span_gen, conv_alpha); - nn_renderer_t nn_renderer(renderer, span_alloc, span_conv); - agg::render_scanlines(rasterizer, scanline, nn_renderer); + auto interpolator = arbitrary_interpolator_t{inverted, dist}; + // C++17 cannot deduce arguments for an alias class template, so define the class explicitly + using span_gen_t = typename type_mapping_t:: + template span_gen_nn_type; + auto span_gen = span_gen_t{image_accessor_clip, interpolator}; + render_image(renderer, rasterizer, span_gen, params.alpha); } } else { agg::image_filter_lut filter; get_filter(params, filter); if (params.is_affine && params.resample) { - using span_gen_t = typename type_mapping_t::template span_gen_affine_type; - using span_conv_t = agg::span_converter; - using int_renderer_t = agg::renderer_scanline_aa; - affine_interpolator_t interpolator(inverted); - span_gen_t span_gen(input_accessor_wrap, interpolator, filter); - span_conv_t span_conv(span_gen, conv_alpha); - int_renderer_t int_renderer(renderer, span_alloc, span_conv); - agg::render_scanlines(rasterizer, scanline, int_renderer); + auto interpolator = agg::span_interpolator_linear{inverted}; + // C++17 cannot deduce arguments for an alias class template, so define the class explicitly + using span_gen_t = typename type_mapping_t:: + template span_gen_affine_type; + auto span_gen = span_gen_t{image_accessor_wrap, interpolator, filter}; + render_image(renderer, rasterizer, span_gen, params.alpha); } else { - using span_gen_t = typename type_mapping_t::template span_gen_filter_type; - using span_conv_t = agg::span_converter; - using int_renderer_t = agg::renderer_scanline_aa; lookup_distortion dist( params.transform_mesh, in_width, in_height, out_width, out_height, false); - arbitrary_interpolator_t interpolator(inverted, dist); - span_gen_t span_gen(input_accessor_wrap, interpolator, filter); - span_conv_t span_conv(span_gen, conv_alpha); - int_renderer_t int_renderer(renderer, span_alloc, span_conv); - agg::render_scanlines(rasterizer, scanline, int_renderer); + auto interpolator = arbitrary_interpolator_t{inverted, dist}; + // C++17 cannot deduce arguments for an alias class template, so define the class explicitly + using span_gen_t = typename type_mapping_t:: + template span_gen_filter_type; + auto span_gen = span_gen_t{image_accessor_wrap, interpolator, filter}; + render_image(renderer, rasterizer, span_gen, params.alpha); } } } diff --git a/src/ft2font.cpp b/src/ft2font.cpp index e99f9a7e1095..e853346bf1f4 100644 --- a/src/ft2font.cpp +++ b/src/ft2font.cpp @@ -16,31 +16,6 @@ #define M_PI 3.14159265358979323846264338328 #endif -/** - To improve the hinting of the fonts, this code uses a hack - presented here: - - http://agg.sourceforge.net/antigrain.com/research/font_rasterization/index.html - - The idea is to limit the effect of hinting in the x-direction, while - preserving hinting in the y-direction. Since freetype does not - support this directly, the dpi in the x-direction is set higher than - in the y-direction, which affects the hinting grid. Then, a global - transform is placed on the font to shrink it back to the desired - size. While it is a bit surprising that the dpi setting affects - hinting, whereas the global transform does not, this is documented - behavior of FreeType, and therefore hopefully unlikely to change. - The FreeType 2 tutorial says: - - NOTE: The transformation is applied to every glyph that is - loaded through FT_Load_Glyph and is completely independent of - any hinting process. This means that you won't get the same - results if you load a glyph at the size of 24 pixels, or a glyph - at the size at 12 pixels scaled by 2 through a transform, - because the hints will have been computed differently (except - you have disabled hints). - */ - FT_Library _ft2Library; FT2Image::FT2Image(unsigned long width, unsigned long height) diff --git a/src/ft2font.h b/src/ft2font.h index 0c438d9107de..09e028f3404c 100644 --- a/src/ft2font.h +++ b/src/ft2font.h @@ -41,7 +41,7 @@ inline char const* ft_error_string(FT_Error error) { #undef __FTERRORS_H__ #define FT_ERROR_START_LIST switch (error) { #define FT_ERRORDEF( e, v, s ) case v: return s; -#define FT_ERROR_END_LIST default: return NULL; } +#define FT_ERROR_END_LIST default: return "unknown error"; } #include FT_ERRORS_H } diff --git a/src/ft2font_wrapper.cpp b/src/ft2font_wrapper.cpp index d0df659c5918..771f1db5a191 100644 --- a/src/ft2font_wrapper.cpp +++ b/src/ft2font_wrapper.cpp @@ -409,9 +409,9 @@ class PyFT2Font final : public FT2Font { std::set::iterator it = family_names.begin(); std::stringstream ss; - ss<<*it; + ss<< (*it ? *it : "unknown family name"); while(++it != family_names.end()){ - ss<<", "<<*it; + ss<<", "<< (*it ? *it : "unknown family name"); } auto text_helpers = py::module_::import("matplotlib._text_helpers"); diff --git a/subprojects/packagefiles/qhull-143.patch b/subprojects/packagefiles/qhull-143.patch index e37a0d28da91..9819f6dd7cae 100644 --- a/subprojects/packagefiles/qhull-143.patch +++ b/subprojects/packagefiles/qhull-143.patch @@ -1,11 +1,13 @@ -From cd8c281da87d38820ecc4c452bbf6fd921155915 Mon Sep 17 00:00:00 2001 +From 61c21986f4ebd1fb68b615ac89231ad1173f6b84 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Thu, 28 Mar 2024 00:54:59 -0400 -Subject: [PATCH 1/3] Annotate printf-like functions with GCC's format +Subject: [PATCH 1/4] Annotate printf-like functions with GCC's format attribute This allows checking format strings when building with `-Wformat` (or with `-Wall`). + +Signed-off-by: Elliott Sales de Andrade --- src/libqhull/libqhull.h | 13 ++++++++++--- src/libqhull_r/libqhull_r.h | 13 ++++++++++--- @@ -13,7 +15,7 @@ with `-Wall`). 3 files changed, 22 insertions(+), 8 deletions(-) diff --git a/src/libqhull/libqhull.h b/src/libqhull/libqhull.h -index 90c0519b..1080ec11 100644 +index 90c0519..1080ec1 100644 --- a/src/libqhull/libqhull.h +++ b/src/libqhull/libqhull.h @@ -60,6 +60,13 @@ @@ -48,7 +50,7 @@ index 90c0519b..1080ec11 100644 /***** -geom.c/geom2.c/random.c prototypes (duplicated from geom.h, random.h) ****************/ diff --git a/src/libqhull_r/libqhull_r.h b/src/libqhull_r/libqhull_r.h -index 023e0181..917f96af 100644 +index 376c1e2..b5185bc 100644 --- a/src/libqhull_r/libqhull_r.h +++ b/src/libqhull_r/libqhull_r.h @@ -48,6 +48,13 @@ @@ -83,7 +85,7 @@ index 023e0181..917f96af 100644 /***** -geom_r.c/geom2_r.c/random_r.c prototypes (duplicated from geom_r.h, random_r.h) ****************/ diff --git a/src/testqset_r/testqset_r.c b/src/testqset_r/testqset_r.c -index 671494f3..b0253e0e 100644 +index 671494f..b0253e0 100644 --- a/src/testqset_r/testqset_r.c +++ b/src/testqset_r/testqset_r.c @@ -117,7 +117,7 @@ int error_count= 0; /* Global error_count. checkSetContents(qh) keeps its own @@ -104,12 +106,16 @@ index 671494f3..b0253e0e 100644 void qh_fprintf(qhT *qh, FILE *fp, int msgcode, const char *fmt, ... ) { static int needs_cr= 0; /* True if qh_fprintf needs a CR. testqset_r is not itself reentrant */ +-- +2.54.0 + -From cc7e366259866d4cd24a312a4aaf891ff0abe85a Mon Sep 17 00:00:00 2001 +From 14f0beeffbfb2505c5b0bf4b8d1b0981025461f5 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Thu, 28 Mar 2024 01:05:06 -0400 -Subject: [PATCH 2/3] Fix arguments inconsistent with their format strings +Subject: [PATCH 2/4] Fix arguments inconsistent with their format strings +Signed-off-by: Elliott Sales de Andrade --- src/libqhull/global.c | 2 +- src/libqhull/merge.c | 11 +++++------ @@ -123,7 +129,7 @@ Subject: [PATCH 2/3] Fix arguments inconsistent with their format strings 9 files changed, 28 insertions(+), 28 deletions(-) diff --git a/src/libqhull/global.c b/src/libqhull/global.c -index 27babbb4..faf67f37 100644 +index 9a81001..47dc46d 100644 --- a/src/libqhull/global.c +++ b/src/libqhull/global.c @@ -2248,7 +2248,7 @@ void qh_lib_check(int qhullLibraryType, int qhTsize, int vertexTsize, int ridgeT @@ -136,7 +142,7 @@ index 27babbb4..faf67f37 100644 } if (last_errcode) { diff --git a/src/libqhull/merge.c b/src/libqhull/merge.c -index de3a0b00..89392dc6 100644 +index de3a0b0..89392dc 100644 --- a/src/libqhull/merge.c +++ b/src/libqhull/merge.c @@ -427,7 +427,7 @@ void qh_appendmergeset(facetT *facet, facetT *neighbor, mergeType mergetype, coo @@ -186,7 +192,7 @@ index de3a0b00..89392dc6 100644 ridge->mergevertex= True; /* disables check for duplicate vertices in qh_checkfacet */ ridgeA->mergevertex= True; diff --git a/src/libqhull/poly2.c b/src/libqhull/poly2.c -index 0bdfa6d6..9077b9c3 100644 +index 4a207b8..b30a2b6 100644 --- a/src/libqhull/poly2.c +++ b/src/libqhull/poly2.c @@ -1144,7 +1144,7 @@ boolT qh_checklists(facetT *facetlist) { @@ -209,7 +215,7 @@ index 0bdfa6d6..9077b9c3 100644 facet->flipped= False; facet->toporient ^= (unsigned char)True; diff --git a/src/libqhull_r/global_r.c b/src/libqhull_r/global_r.c -index 3a0b9c62..c681a715 100644 +index 04b9b4d..01dfe8e 100644 --- a/src/libqhull_r/global_r.c +++ b/src/libqhull_r/global_r.c @@ -2201,7 +2201,7 @@ void qh_lib_check(int qhullLibraryType, int qhTsize, int vertexTsize, int ridgeT @@ -222,7 +228,7 @@ index 3a0b9c62..c681a715 100644 } if (last_errcode) { diff --git a/src/libqhull_r/mem_r.c b/src/libqhull_r/mem_r.c -index 7d5509eb..d811f733 100644 +index 7d5509e..d811f73 100644 --- a/src/libqhull_r/mem_r.c +++ b/src/libqhull_r/mem_r.c @@ -186,7 +186,7 @@ void qh_memcheck(qhT *qh) { @@ -244,7 +250,7 @@ index 7d5509eb..d811f733 100644 /*-mergevertex= True; /* disables check for duplicate vertices in qh_checkfacet */ ridgeA->mergevertex= True; diff --git a/src/libqhull_r/poly2_r.c b/src/libqhull_r/poly2_r.c -index 01758340..4d9a0c51 100644 +index 1ab5244..a97254e 100644 --- a/src/libqhull_r/poly2_r.c +++ b/src/libqhull_r/poly2_r.c @@ -1145,7 +1145,7 @@ boolT qh_checklists(qhT *qh, facetT *facetlist) { @@ -317,7 +323,7 @@ index 01758340..4d9a0c51 100644 facet->flipped= False; facet->toporient ^= (unsigned char)True; diff --git a/src/libqhullcpp/Qhull.cpp b/src/libqhullcpp/Qhull.cpp -index d5c75e92..3123e8ae 100644 +index d5c75e9..3123e8a 100644 --- a/src/libqhullcpp/Qhull.cpp +++ b/src/libqhullcpp/Qhull.cpp @@ -357,7 +357,7 @@ initializeFeasiblePoint(int hulldim) @@ -330,7 +336,7 @@ index d5c75e92..3123e8ae 100644 } qh_qh->feasible_point= static_cast(qh_malloc(static_cast(hulldim) * sizeof(coordT))); diff --git a/src/testqset_r/testqset_r.c b/src/testqset_r/testqset_r.c -index b0253e0e..5ea87394 100644 +index b0253e0..5ea8739 100644 --- a/src/testqset_r/testqset_r.c +++ b/src/testqset_r/testqset_r.c @@ -532,7 +532,7 @@ void testSetequalInEtc(qhT *qh, int numInts, int *intarray, int checkEvery) @@ -380,19 +386,23 @@ index b0253e0e..5ea87394 100644 error_count++; } } +-- +2.54.0 -From f7c3bbdfd23c034f5af66fa1f067691aac3378d8 Mon Sep 17 00:00:00 2001 + +From 2dda51b2f2ec394462cb95ce37b5afc9701c8446 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Thu, 28 Mar 2024 05:11:33 -0400 -Subject: [PATCH 3/3] Don't pass user-defined input as format string +Subject: [PATCH 3/4] Don't pass user-defined input as format string +Signed-off-by: Elliott Sales de Andrade --- src/libqhull/io.c | 2 +- src/libqhull_r/io_r.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libqhull/io.c b/src/libqhull/io.c -index beed156a..7b7f4546 100644 +index beed156..7b7f454 100644 --- a/src/libqhull/io.c +++ b/src/libqhull/io.c @@ -1618,7 +1618,7 @@ void qh_printcenter(FILE *fp, qh_PRINT format, const char *string, facetT *facet @@ -405,7 +415,7 @@ index beed156a..7b7f4546 100644 num= qh hull_dim-1; if (!facet->normal || !facet->upperdelaunay || !qh ATinfinity) { diff --git a/src/libqhull_r/io_r.c b/src/libqhull_r/io_r.c -index a80a5b14..389b1aa6 100644 +index a80a5b1..389b1aa 100644 --- a/src/libqhull_r/io_r.c +++ b/src/libqhull_r/io_r.c @@ -1618,7 +1618,7 @@ void qh_printcenter(qhT *qh, FILE *fp, qh_PRINT format, const char *string, face @@ -417,3 +427,48 @@ index a80a5b14..389b1aa6 100644 if (qh->CENTERtype == qh_ASvoronoi) { num= qh->hull_dim-1; if (!facet->normal || !facet->upperdelaunay || !qh->ATinfinity) { +-- +2.54.0 + + +From b6d5a184cd64d160a50d799ad8b179efeffed121 Mon Sep 17 00:00:00 2001 +From: Brad Barber +Date: Sun, 7 Sep 2025 14:52:49 -0400 +Subject: [PATCH 4/4] poly2.c, poly2_r.c: fixed missing + 'getid_(previousvertex)' for #143 + +Signed-off-by: Elliott Sales de Andrade +--- + src/libqhull/poly2.c | 2 +- + src/libqhull_r/poly2_r.c | 2 +- + 2 files changed, 2 insertions(+), 2 deletions(-) + +diff --git a/src/libqhull/poly2.c b/src/libqhull/poly2.c +index b30a2b6..f70180e 100644 +--- a/src/libqhull/poly2.c ++++ b/src/libqhull/poly2.c +@@ -1144,7 +1144,7 @@ boolT qh_checklists(facetT *facetlist) { + vertex->visitid= qh vertex_visit; + if (vertex->previous != previousvertex) { + qh_fprintf(qh ferr, 6427, "qhull internal error (qh_checklists): expecting v%d.previous == v%d. Got v%d\n", +- vertex->id, previousvertex->id, getid_(vertex->previous)); ++ vertex->id, getid_(previousvertex), getid_(vertex->previous)); + waserror= True; + errorvertex= vertex; + } +diff --git a/src/libqhull_r/poly2_r.c b/src/libqhull_r/poly2_r.c +index a97254e..44110db 100644 +--- a/src/libqhull_r/poly2_r.c ++++ b/src/libqhull_r/poly2_r.c +@@ -1145,7 +1145,7 @@ boolT qh_checklists(qhT *qh, facetT *facetlist) { + vertex->visitid= qh->vertex_visit; + if (vertex->previous != previousvertex) { + qh_fprintf(qh, qh->ferr, 6427, "qhull internal error (qh_checklists): expecting v%d.previous == v%d. Got v%d\n", +- vertex->id, previousvertex->id, getid_(vertex->previous)); ++ vertex->id, getid_(previousvertex), getid_(vertex->previous)); + waserror= True; + errorvertex= vertex; + } +-- +2.54.0 + diff --git a/tools/boilerplate.py b/tools/boilerplate.py index 0a1a26c7cb76..2ffe86080143 100644 --- a/tools/boilerplate.py +++ b/tools/boilerplate.py @@ -11,7 +11,7 @@ of Figure and Axes. Whenever the API of one of the wrapped methods changes, this script has to be rerun to keep pyplot.py up to date. -The test ``lib/matplotlib/test_pyplot.py::test_pyplot_up_to_date`` checks +The test ``lib/matplotlib/tests/test_pyplot.py::test_pyplot_up_to_date`` checks that the autogenerated part of pyplot.py is up to date. It will fail in the case of an API mismatch and remind the developer to rerun this script. """ @@ -95,6 +95,8 @@ def __init__(self, value): self._repr = "np.mean" elif value is _api.deprecation._deprecated_parameter: self._repr = "_api.deprecation._deprecated_parameter" + elif value is _api.UNSET: + self._repr = "_UNSET" elif isinstance(value, Enum): # Enum str is Class.Name whereas their repr is . self._repr = f'{type(value).__name__}.{value.name}' diff --git a/tools/embed_js.py b/tools/embed_js.py deleted file mode 100644 index 571bf80238e9..000000000000 --- a/tools/embed_js.py +++ /dev/null @@ -1,102 +0,0 @@ -""" -Script to embed JavaScript dependencies in mpl.js. -""" - -from collections import namedtuple -from pathlib import Path -import re -import shutil -import subprocess -import sys - - -Package = namedtuple('Package', [ - # The package to embed, in some form that `npm install` can use. - 'name', - # The path to the source file within the package to embed. - 'source', - # The path to the license file within the package to embed. - 'license']) -# The list of packages to embed, in some form that `npm install` can use. -JAVASCRIPT_PACKAGES = [ - # Polyfill/ponyfill for ResizeObserver. - Package('@jsxtools/resize-observer', 'index.js', 'LICENSE.md'), -] -# This is the magic line that must exist in mpl.js, after which the embedded -# JavaScript will be appended. -MPLJS_MAGIC_HEADER = ( - "///////////////// REMAINING CONTENT GENERATED BY embed_js.py " - "/////////////////\n") - - -def safe_name(name): - """ - Make *name* safe to use as a JavaScript variable name. - """ - return '_'.join(re.split(r'[@/-]', name)).upper() - - -def prep_package(web_backend_path, pkg): - source = web_backend_path / 'node_modules' / pkg.name / pkg.source - license = web_backend_path / 'node_modules' / pkg.name / pkg.license - if not source.exists(): - # Exact version should already be saved in package.json, so we use - # --no-save here. - try: - subprocess.run(['npm', 'install', '--no-save', pkg.name], - cwd=web_backend_path) - except FileNotFoundError as err: - raise ValueError( - f'npm must be installed to fetch {pkg.name}') from err - if not source.exists(): - raise ValueError( - f'{pkg.name} package is missing source in {pkg.source}') - elif not license.exists(): - raise ValueError( - f'{pkg.name} package is missing license in {pkg.license}') - - return source, license - - -def gen_embedded_lines(pkg, source): - name = safe_name(pkg.name) - print('Embedding', source, 'as', name) - yield '// prettier-ignore\n' - for line in source.read_text().splitlines(): - yield (line.replace('module.exports=function', f'var {name}=function') - + ' // eslint-disable-line\n') - - -def build_mpljs(web_backend_path, license_path): - mpljs_path = web_backend_path / "js/mpl.js" - mpljs_orig = mpljs_path.read_text().splitlines(keepends=True) - try: - mpljs_orig = mpljs_orig[:mpljs_orig.index(MPLJS_MAGIC_HEADER) + 1] - except IndexError as err: - raise ValueError( - f'The mpl.js file *must* have the exact line: {MPLJS_MAGIC_HEADER}' - ) from err - - with mpljs_path.open('w') as mpljs: - mpljs.writelines(mpljs_orig) - - for pkg in JAVASCRIPT_PACKAGES: - source, license = prep_package(web_backend_path, pkg) - mpljs.writelines(gen_embedded_lines(pkg, source)) - - shutil.copy(license, - license_path / f'LICENSE{safe_name(pkg.name)}') - - -if __name__ == '__main__': - # Write the mpl.js file. - if len(sys.argv) > 1: - web_backend_path = Path(sys.argv[1]) - else: - web_backend_path = (Path(__file__).parent.parent / - "lib/matplotlib/backends/web_backend") - if len(sys.argv) > 2: - license_path = Path(sys.argv[2]) - else: - license_path = Path(__file__).parent.parent / "LICENSE" - build_mpljs(web_backend_path, license_path) diff --git a/tools/github_stats.py b/tools/github_stats.py index af0255fcefba..6e442d220180 100755 --- a/tools/github_stats.py +++ b/tools/github_stats.py @@ -28,6 +28,8 @@ PER_PAGE = 100 REPORT_TEMPLATE = """\ +.. redirect-from:: /users/github_stats + .. _github-stats: {title}