diff --git a/.github/workflows/cibuildwheel.yml b/.github/workflows/cibuildwheel.yml
index 9ee39d0c7841..b567c015dc2c 100644
--- a/.github/workflows/cibuildwheel.yml
+++ b/.github/workflows/cibuildwheel.yml
@@ -28,21 +28,31 @@ jobs:
- name: Install cibuildwheel
run: |
- python -m pip install cibuildwheel==1.5.5
+ python -m pip install cibuildwheel==1.6.3
- name: Copy setup.cfg to configure wheel
run: |
cp setup.cfg.template setup.cfg
+ - name: Build wheels for CPython 3.9
+ run: |
+ python -m cibuildwheel --output-dir dist
+ env:
+ CIBW_BUILD: "cp39-*"
+ CIBW_MANYLINUX_X86_64_IMAGE: manylinux1
+ CIBW_MANYLINUX_I686_IMAGE: manylinux1
+ CIBW_BEFORE_BUILD: pip install certifi numpy==1.19.3
+ MPL_DISABLE_FH4: "yes"
+
- name: Build wheels for CPython
run: |
python -m cibuildwheel --output-dir dist
env:
CIBW_BUILD: "cp3?-*"
- CIBW_SKIP: "cp35-* cp36-*"
+ CIBW_SKIP: "cp35-* cp36-* cp39-*"
CIBW_MANYLINUX_X86_64_IMAGE: manylinux1
CIBW_MANYLINUX_I686_IMAGE: manylinux1
- CIBW_BEFORE_BUILD: pip install numpy==1.15
+ CIBW_BEFORE_BUILD: pip install certifi numpy==1.15
MPL_DISABLE_FH4: "yes"
- name: Build wheels for CPython 3.6
@@ -52,7 +62,7 @@ jobs:
CIBW_BUILD: "cp36-*"
CIBW_MANYLINUX_X86_64_IMAGE: manylinux1
CIBW_MANYLINUX_I686_IMAGE: manylinux1
- CIBW_BEFORE_BUILD: pip install numpy==1.15
+ CIBW_BEFORE_BUILD: pip install certifi numpy==1.15
MPL_DISABLE_FH4: "yes"
if: >
startsWith(github.ref, 'refs/heads/v3.3') ||
@@ -63,7 +73,7 @@ jobs:
python -m cibuildwheel --output-dir dist
env:
CIBW_BUILD: "pp3?-*"
- CIBW_BEFORE_BUILD: pip install numpy==1.15
+ CIBW_BEFORE_BUILD: pip install certifi numpy==1.15
if: >
runner.os != 'Windows' && (
startsWith(github.ref, 'refs/heads/v3.3') ||
diff --git a/.github/workflows/reviewdog.yml b/.github/workflows/reviewdog.yml
index ba142edd6aed..9ec0abf942ba 100644
--- a/.github/workflows/reviewdog.yml
+++ b/.github/workflows/reviewdog.yml
@@ -18,11 +18,11 @@ jobs:
- name: Set up reviewdog
run: |
- mkdir -p $HOME/bin
+ mkdir -p "$HOME/bin"
curl -sfL \
https://github.com/reviewdog/reviewdog/raw/master/install.sh | \
- sh -s -- -b $HOME/bin
- echo ::add-path::$HOME/bin
+ sh -s -- -b "$HOME/bin"
+ echo "$HOME/bin" >> $GITHUB_PATH
- name: Run flake8
env:
diff --git a/.travis.yml b/.travis.yml
index d3b9780d8105..59e6f5b3fb5f 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -87,6 +87,7 @@ matrix:
env:
- PRE=--pre
- os: osx
+ osx_image: xcode9
language: generic # https://github.com/travis-ci/travis-ci/issues/2312
only: master
cache:
@@ -101,26 +102,15 @@ matrix:
allow_failures:
- python: "nightly"
-before_install: |
+before_install:
+- |
+ # Install OS dependencies and set up ccache
case "$TRAVIS_OS_NAME" in
linux)
export PATH=/usr/lib/ccache:$PATH
;;
osx)
- set -e
- ci/silence brew update
- brew uninstall numpy gdal postgis
- brew unlink python@2
- brew install python || brew upgrade python
- brew install ffmpeg imagemagick mplayer ccache
- hash -r
- which python
- python --version
- set +e
- # We could install ghostscript and inkscape here to test svg and pdf
- # but this makes the test time really long.
- # brew install ghostscript inkscape
- export PATH=/usr/local/opt/python/libexec/bin:/usr/local/opt/ccache/libexec:$PATH
+ ci/osx-deps
;;
esac
@@ -143,19 +133,28 @@ install:
# install was successful by trying to import the toolkit (sometimes, the
# install appears to be successful but shared libraries cannot be loaded at
# runtime, so an actual import is a better check).
- python -mpip install --upgrade pycairo cairocffi>=0.8
- python -mpip install --upgrade PyGObject &&
- python -c 'import gi; gi.require_version("Gtk", "3.0"); from gi.repository import Gtk' &&
- echo 'PyGObject is available' ||
- echo 'PyGObject is not available'
- python -mpip install --upgrade pyqt5 &&
- python -c 'import PyQt5.QtCore' &&
- echo 'PyQt5 is available' ||
- echo 'PyQt5 is not available'
- python -mpip install --upgrade pyside2 &&
- python -c 'import PySide2.QtCore' &&
- echo 'PySide2 is available' ||
- echo 'PySide2 is not available'
+
+ # PyGObject, pycairo, and cariocffi do not install on OSX 10.12
+
+ # There are not functioning wheels available for OSX 10.12 (as of
+ # Sept 2020) for either pyqt5 (there are only wheels for 10.13+)
+ # or pyside2 (the latest version (5.13.2) with 10.12 wheels has a
+ # fatal to us bug, it was fixed in 5.14.0 which has 10.13 wheels)
+ if [[ $TRAVIS_OS_NAME != 'osx' ]]; then
+ python -mpip install --upgrade pycairo cairocffi>=0.8
+ python -mpip install --upgrade PyGObject &&
+ python -c 'import gi; gi.require_version("Gtk", "3.0"); from gi.repository import Gtk' &&
+ echo 'PyGObject is available' ||
+ echo 'PyGObject is not available'
+ python -mpip install --upgrade pyqt5 &&
+ python -c 'import PyQt5.QtCore' &&
+ echo 'PyQt5 is available' ||
+ echo 'PyQt5 is not available'
+ python -mpip install --upgrade pyside2 &&
+ python -c 'import PySide2.QtCore' &&
+ echo 'PySide2 is available' ||
+ echo 'PySide2 is not available'
+ fi
python -mpip install --upgrade \
-f https://extras.wxpython.org/wxPython4/extras/linux/gtk3/ubuntu-16.04 \
wxPython &&
@@ -169,7 +168,7 @@ install:
export CPPFLAGS=--coverage
fi
- |
- python -mpip install -ve . # Install Matplotlib.
+ python -mpip install -e . # Install Matplotlib.
- |
if [[ $TRAVIS_OS_NAME != 'osx' ]]; then
unset CPPFLAGS
diff --git a/LICENSE/LICENSE_JSXTOOLS_RESIZE_OBSERVER b/LICENSE/LICENSE_JSXTOOLS_RESIZE_OBSERVER
new file mode 100644
index 000000000000..0bc1fa7060b7
--- /dev/null
+++ b/LICENSE/LICENSE_JSXTOOLS_RESIZE_OBSERVER
@@ -0,0 +1,108 @@
+# CC0 1.0 Universal
+
+## Statement of Purpose
+
+The laws of most jurisdictions throughout the world automatically confer
+exclusive Copyright and Related Rights (defined below) upon the creator and
+subsequent owner(s) (each and all, an “owner”) of an original work of
+authorship and/or a database (each, a “Work”).
+
+Certain owners wish to permanently relinquish those rights to a Work for the
+purpose of contributing to a commons of creative, cultural and scientific works
+(“Commons”) that the public can reliably and without fear of later claims of
+infringement build upon, modify, incorporate in other works, reuse and
+redistribute as freely as possible in any form whatsoever and for any purposes,
+including without limitation commercial purposes. These owners may contribute
+to the Commons to promote the ideal of a free culture and the further
+production of creative, cultural and scientific works, or to gain reputation or
+greater distribution for their Work in part through the use and efforts of
+others.
+
+For these and/or other purposes and motivations, and without any expectation of
+additional consideration or compensation, the person associating CC0 with a
+Work (the “Affirmer”), to the extent that he or she is an owner of Copyright
+and Related Rights in the Work, voluntarily elects to apply CC0 to the Work and
+publicly distribute the Work under its terms, with knowledge of his or her
+Copyright and Related Rights in the Work and the meaning and intended legal
+effect of CC0 on those rights.
+
+1. Copyright and Related Rights. A Work made available under CC0 may be
+ protected by copyright and related or neighboring rights (“Copyright and
+ Related Rights”). Copyright and Related Rights include, but are not limited
+ to, the following:
+ 1. the right to reproduce, adapt, distribute, perform, display, communicate,
+ and translate a Work;
+ 2. moral rights retained by the original author(s) and/or performer(s);
+ 3. publicity and privacy rights pertaining to a person’s image or likeness
+ depicted in a Work;
+ 4. rights protecting against unfair competition in regards to a Work,
+ subject to the limitations in paragraph 4(i), below;
+ 5. rights protecting the extraction, dissemination, use and reuse of data in
+ a Work;
+ 6. database rights (such as those arising under Directive 96/9/EC of the
+ European Parliament and of the Council of 11 March 1996 on the legal
+ protection of databases, and under any national implementation thereof,
+ including any amended or successor version of such directive); and
+ 7. other similar, equivalent or corresponding rights throughout the world
+ based on applicable law or treaty, and any national implementations
+ thereof.
+
+2. Waiver. To the greatest extent permitted by, but not in contravention of,
+ applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and
+ unconditionally waives, abandons, and surrenders all of Affirmer’s Copyright
+ and Related Rights and associated claims and causes of action, whether now
+ known or unknown (including existing as well as future claims and causes of
+ action), in the Work (i) in all territories worldwide, (ii) for the maximum
+ duration provided by applicable law or treaty (including future time
+ extensions), (iii) in any current or future medium and for any number of
+ copies, and (iv) for any purpose whatsoever, including without limitation
+ commercial, advertising or promotional purposes (the “Waiver”). Affirmer
+ makes the Waiver for the benefit of each member of the public at large and
+ to the detriment of Affirmer’s heirs and successors, fully intending that
+ such Waiver shall not be subject to revocation, rescission, cancellation,
+ termination, or any other legal or equitable action to disrupt the quiet
+ enjoyment of the Work by the public as contemplated by Affirmer’s express
+ Statement of Purpose.
+
+3. Public License Fallback. Should any part of the Waiver for any reason be
+ judged legally invalid or ineffective under applicable law, then the Waiver
+ shall be preserved to the maximum extent permitted taking into account
+ Affirmer’s express Statement of Purpose. In addition, to the extent the
+ Waiver is so judged Affirmer hereby grants to each affected person a
+ royalty-free, non transferable, non sublicensable, non exclusive,
+ irrevocable and unconditional license to exercise Affirmer’s Copyright and
+ Related Rights in the Work (i) in all territories worldwide, (ii) for the
+ maximum duration provided by applicable law or treaty (including future time
+ extensions), (iii) in any current or future medium and for any number of
+ copies, and (iv) for any purpose whatsoever, including without limitation
+ commercial, advertising or promotional purposes (the “License”). The License
+ shall be deemed effective as of the date CC0 was applied by Affirmer to the
+ Work. Should any part of the License for any reason be judged legally
+ invalid or ineffective under applicable law, such partial invalidity or
+ ineffectiveness shall not invalidate the remainder of the License, and in
+ such case Affirmer hereby affirms that he or she will not (i) exercise any
+ of his or her remaining Copyright and Related Rights in the Work or (ii)
+ assert any associated claims and causes of action with respect to the Work,
+ in either case contrary to Affirmer’s express Statement of Purpose.
+
+4. Limitations and Disclaimers.
+ 1. No trademark or patent rights held by Affirmer are waived, abandoned,
+ surrendered, licensed or otherwise affected by this document.
+ 2. Affirmer offers the Work as-is and makes no representations or warranties
+ of any kind concerning the Work, express, implied, statutory or
+ otherwise, including without limitation warranties of title,
+ merchantability, fitness for a particular purpose, non infringement, or
+ the absence of latent or other defects, accuracy, or the present or
+ absence of errors, whether or not discoverable, all to the greatest
+ extent permissible under applicable law.
+ 3. Affirmer disclaims responsibility for clearing rights of other persons
+ that may apply to the Work or any use thereof, including without
+ limitation any person’s Copyright and Related Rights in the Work.
+ Further, Affirmer disclaims responsibility for obtaining any necessary
+ consents, permissions or other rights required for any use of the Work.
+ 4. Affirmer understands and acknowledges that Creative Commons is not a
+ party to this document and has no duty or obligation with respect to this
+ CC0 or use of the Work.
+
+For more information, please see
+http://creativecommons.org/publicdomain/zero/1.0/.
diff --git a/ci/osx-deps b/ci/osx-deps
new file mode 100755
index 000000000000..2f00bb9ffa67
--- /dev/null
+++ b/ci/osx-deps
@@ -0,0 +1,65 @@
+#!/bin/bash
+
+set -euo pipefail
+cache="$HOME"/.cache/matplotlib
+
+fold_start() {
+ key=$1
+ title=$2
+ echo -e "travis_fold:start:$key\e[2K"
+ echo -e "travis_time:start:$key\e[2K"
+ tick="$(date +%s)"
+ echo "$title"
+}
+
+fold_end() {
+ key=$1
+ tock="$(date +%s)"
+ nano=000000000
+ echo -e "travis_time:end:$key:start=$tick$nano,finish=$tock$nano,duration=$((tock - tick))$nano\e[2K"
+ echo -e "travis_fold:end:$key\e[2K"
+}
+
+cached_download() {
+ file=$1
+ url=$2
+ shasum=$3
+ path="$cache/$file"
+ if [[ ! -f "$path"
+ || "$(shasum -a 256 "$path" | awk '{print $1}')" != "$shasum" ]]
+ then
+ curl -L -o "$path" "$url"
+ fi
+}
+
+fold_start Python "Install Python 3.8 from python.org"
+cached_download python-3.8.5-macosx10.9.pkg \
+ https://www.python.org/ftp/python/3.8.5/python-3.8.5-macosx10.9.pkg \
+ e27c5a510c10f830084fb9c60b9e9aa8719d92e4537a80e6b4252c02396f0d29
+sudo installer -package "$cache"/python-3.8.5-macosx10.9.pkg -target /
+sudo ln -s /usr/local/bin/python3 /usr/local/bin/python
+hash -r
+fold_end Python
+
+fold_start ccache 'Install ccache (compile it ourselves)'
+cached_download ccache-3.7.11.tar.xz \
+ https://github.com/ccache/ccache/releases/download/v3.7.11/ccache-3.7.11.tar.xz \
+ 8d450208099a4d202bd7df87caaec81baee20ce9dd62da91e9ea7b95a9072f68
+tar xf "$cache"/ccache-3.7.11.tar.xz
+pushd ccache-3.7.11
+./configure --prefix=/usr/local
+make -j2
+make install
+popd
+for compiler in clang clang++ cc gcc c++ g++; do
+ ln -sf ccache /usr/local/bin/$compiler
+done
+fold_end ccache
+
+fold_start freetype 'Install freetype (just unpack into the build directory)'
+cached_download freetype-2.6.1.tar.gz \
+ https://download.savannah.gnu.org/releases/freetype/freetype-2.6.1.tar.gz \
+ 0a3c7dfbda6da1e8fce29232e8e96d987ababbbf71ebc8c75659e4132c367014
+mkdir -p build
+tar -x -C build -f "$cache"/freetype-2.6.1.tar.gz
+fold_end freetype
diff --git a/doc/_static/zenodo_cache/4030140.svg b/doc/_static/zenodo_cache/4030140.svg
new file mode 100644
index 000000000000..8fcb71dead83
--- /dev/null
+++ b/doc/_static/zenodo_cache/4030140.svg
@@ -0,0 +1,35 @@
+
\ No newline at end of file
diff --git a/doc/api/prev_api_changes/api_changes_3.1.0.rst b/doc/api/prev_api_changes/api_changes_3.1.0.rst
index 2c0f629729db..b6e3ff8c4733 100644
--- a/doc/api/prev_api_changes/api_changes_3.1.0.rst
+++ b/doc/api/prev_api_changes/api_changes_3.1.0.rst
@@ -727,7 +727,7 @@ Mathtext changes
Deprecations
~~~~~~~~~~~~
-- The ``\stackrel`` mathtext command hsa been deprecated (it behaved differently
+- The ``\stackrel`` mathtext command has been deprecated (it behaved differently
from LaTeX's ``\stackrel``. To stack two mathtext expressions, use
``\genfrac{left-delim}{right-delim}{fraction-bar-thickness}{}{top}{bottom}``.
- The ``\mathcircled`` mathtext command (which is not a real TeX command)
diff --git a/doc/citing.rst b/doc/citing.rst
index cea1c2b96319..cdc4c60fcbd0 100644
--- a/doc/citing.rst
+++ b/doc/citing.rst
@@ -39,6 +39,9 @@ By version
.. START OF AUTOGENERATED
+v3.3.2
+ .. image:: _static/zenodo_cache/4030140.svg
+ :target: https://doi.org/10.5281/zenodo.4030140
v3.3.1
.. image:: _static/zenodo_cache/3984190.svg
:target: https://doi.org/10.5281/zenodo.3984190
diff --git a/doc/users/github_stats.rst b/doc/users/github_stats.rst
index 7ef8dd1111b6..ab8f8384b8aa 100644
--- a/doc/users/github_stats.rst
+++ b/doc/users/github_stats.rst
@@ -3,91 +3,94 @@
GitHub Stats
============
-GitHub stats for 2020/08/14 - 2020/09/15 (tag: v3.3.1)
+GitHub stats for 2020/09/15 - 2020/11/11 (tag: v3.3.2)
These lists are automatically generated, and may be incomplete or contain duplicates.
-We closed 15 issues and merged 39 pull requests.
-The full list can be seen `on GitHub `__
+We closed 14 issues and merged 46 pull requests.
+The full list can be seen `on GitHub `__
-The following 14 authors contributed 61 commits.
+The following 11 authors contributed 73 commits.
* Antony Lee
-* Bruno Beltran
* David Stansby
-* David Young
* Elliott Sales de Andrade
-* Greg Lucas
+* Eric Larson
* Jody Klymak
-* johnthagen
* Jouni K. Seppänen
-* Richard Sheridan
-* richardsheridan
* Ryan May
+* shevawen
+* Stephen Sinclair
* Thomas A Caswell
* Tim Hoffmann
GitHub issues and pull requests:
-Pull Requests (39):
-
-* :ghpull:`18488`: Backport PR #18483 on branch v3.3.x (DOC: reword non-monotonic cell center warning)
-* :ghpull:`18483`: DOC: reword non-monotonic cell center warning
-* :ghpull:`18485`: Backport PR #18475 on branch v3.3.x (BF: ensure exception caught if no kpeswitch)
-* :ghpull:`18482`: Backport PR #18398 on branch v3.3.x (Warn on non-increasing/decreasing pcolor coords)
-* :ghpull:`18484`: Backport PR #18458: Fix huge imshow range
-* :ghpull:`18475`: BF: ensure exception caught if no kpeswitch
-* :ghpull:`18458`: Fix huge imshow range
-* :ghpull:`18398`: Warn on non-increasing/decreasing pcolor coords
-* :ghpull:`18479`: Nbagg backports
-* :ghpull:`18454`: nbagg: Use OutputArea event to trigger figure close.
-* :ghpull:`18469`: Backport PR #18464 on branch v3.3.x (Remove extra stickies in barstacked histogram.)
-* :ghpull:`18464`: Remove extra stickies in barstacked histogram.
-* :ghpull:`18459`: Backport PR #18393 on branch v3.3.x (Fix Axis scale on twinned Axes.)
-* :ghpull:`18393`: Fix Axis scale on twinned Axes.
-* :ghpull:`18441`: Backport PR #18395: TkAgg bugfix: deselect buttons that are not the current _Mode
-* :ghpull:`18395`: TkAgg bugfix: deselect buttons that are not the current _Mode
-* :ghpull:`18380`: Backport PR #18374 on branch v3.3.x (FIX: make _reshape_2D accept pandas df with string indices)
-* :ghpull:`18374`: FIX: make _reshape_2D accept pandas df with string indices
-* :ghpull:`18376`: Backport PR #18298 on branch v3.3.x (Include license files in built distribution)
-* :ghpull:`18375`: Backport PR #18293 on branch v3.3.x (Fix scatter3d color/linewidth re-projection)
-* :ghpull:`18298`: Include license files in built distribution
-* :ghpull:`18293`: Fix scatter3d color/linewidth re-projection
-* :ghpull:`18361`: nbagg: Store DPI ratio on figure instead of window.
-* :ghpull:`18354`: Backport PR #18352 on branch v3.3.x (Avoid triggering backend resolution during qt initial import.)
-* :ghpull:`18352`: Avoid triggering backend resolution during qt initial import.
-* :ghpull:`18335`: Backport PR #18322 on branch v3.3.x (Disable FH4 so that we don't require VCRUNTIME140_1.dll.)
-* :ghpull:`18322`: Disable FH4 so that we don't require VCRUNTIME140_1.dll.
-* :ghpull:`18333`: Backport PR #18328 on branch v3.3.x (Add missing check for None in Qt toolmanager.)
-* :ghpull:`18328`: Add missing check for None in Qt toolmanager.
-* :ghpull:`18309`: Backport PR #18304 on branch v3.3.x (Fix canvas redraws during motion in figures with a Button or TextBox)
-* :ghpull:`18304`: Fix canvas redraws during motion in figures with a Button or TextBox
-* :ghpull:`18297`: Backport PR #18288 on branch v3.3.x (FIX: check if axes is off page before repositioning title)
-* :ghpull:`18288`: FIX: check if axes is off page before repositioning title
-* :ghpull:`18269`: Backport PR #18266 on branch v3.3.x (Fix Path.get_extents for empty paths.)
-* :ghpull:`18266`: Fix Path.get_extents for empty paths.
-* :ghpull:`18263`: Backport PR #18260 on branch v3.3.x (Add parent widget to IntVar)
-* :ghpull:`18260`: Add parent widget to IntVar
-* :ghpull:`18253`: Backport PR #18245 on branch v3.3.x
-* :ghpull:`18245`: MNT: do a better job guessing the GUI framework in use
-
-Issues (15):
-
-* :ghissue:`18415`: imshow with LogNorm crashes with certain inputs
-* :ghissue:`18447`: nbagg: Closing a figure from the notebook does not close the python figure
-* :ghissue:`18470`: interactive plots slow with matplotlib 3.3.1
-* :ghissue:`18457`: Incorrect log y-scale for histogram with partitioned and barstacked data
-* :ghissue:`18385`: twinx not respecting log-scale
-* :ghissue:`18371`: Plotting a pandas DataFrame with string MultiIndex
-* :ghissue:`18296`: LICENSE file(s) not included in published PyPI package
-* :ghissue:`18287`: scatter3D assigns wrong color to points for some plot orientations
-* :ghissue:`18292`: ImportError: DLL load failed with Matplotlib 3.3.1 on Windows
-* :ghissue:`18327`: Tool Manager: adding buttons to toolbar fails with matplotlib version 3.3.1 using Qt backend
-* :ghissue:`18324`: Poor UI responsiveness of 3.3.1 compared with 3.2.2 for interactive mode UI using widgets
-* :ghissue:`18303`: Canvas redraws during any motion when Button is present
-* :ghissue:`18283`: Automatic title placement wrong if parent axes is off the page
-* :ghissue:`18254`: scatter(..., marker='') raises on drawing with mpl3.3.1
-* :ghissue:`18259`: New IntVar needs a parent widget
+Pull Requests (46):
+
+* :ghpull:`18936`: Backport PR #18929 on branch v3.3.x
+* :ghpull:`18929`: FIX: make sure scalarmappable updates are handled correctly in 3D
+* :ghpull:`18928`: Backport PR #18842 on branch v3.3.x (Add CPython 3.9 wheels.)
+* :ghpull:`18842`: Add CPython 3.9 wheels.
+* :ghpull:`18921`: Backport PR #18732 on branch v3.3.x (Add a ponyfill for ResizeObserver on older browsers.)
+* :ghpull:`18732`: Add a ponyfill for ResizeObserver on older browsers.
+* :ghpull:`18886`: Backport #18860 on branch v3.3.x
+* :ghpull:`18860`: FIX: stop deprecation message colorbar
+* :ghpull:`18845`: Backport PR #18839 on branch v3.3.x
+* :ghpull:`18843`: Backport PR #18756 on branch v3.3.x (FIX: improve date performance regression)
+* :ghpull:`18850`: Backport CI fixes to v3.3.x
+* :ghpull:`18839`: MNT: make sure we do not mutate input in Text.update
+* :ghpull:`18838`: Fix ax.set_xticklabels(fontproperties=fp)
+* :ghpull:`18756`: FIX: improve date performance regression
+* :ghpull:`18787`: Backport PR #18769 on branch v3.3.x
+* :ghpull:`18786`: Backport PR #18754 on branch v3.3.x (FIX: make sure we have more than 1 tick with small log ranges)
+* :ghpull:`18754`: FIX: make sure we have more than 1 tick with small log ranges
+* :ghpull:`18769`: Support ``ax.grid(visible=)``.
+* :ghpull:`18778`: Backport PR #18773 on branch v3.3.x (Update to latest cibuildwheel release.)
+* :ghpull:`18773`: Update to latest cibuildwheel release.
+* :ghpull:`18755`: Backport PR #18734 on branch v3.3.x (Fix deprecation warning in GitHub Actions.)
+* :ghpull:`18734`: Fix deprecation warning in GitHub Actions.
+* :ghpull:`18725`: Backport PR #18533 on branch v3.3.x
+* :ghpull:`18723`: Backport PR #18584 on branch v3.3.x (Fix setting 0-timeout timer with Tornado.)
+* :ghpull:`18676`: Backport PR #18670 on branch v3.3.x (MNT: make certifi actually optional)
+* :ghpull:`18670`: MNT: make certifi actually optional
+* :ghpull:`18665`: Backport PR #18639 on branch v3.3.x (nbagg: Don't close figures for bubbled events.)
+* :ghpull:`18639`: nbagg: Don't close figures for bubbled events.
+* :ghpull:`18640`: Backport PR #18636 on branch v3.3.x (BLD: certifi is not a run-time dependency)
+* :ghpull:`18636`: BLD: certifi is not a run-time dependency
+* :ghpull:`18629`: Backport PR #18621 on branch v3.3.x (Fix singleshot timers in wx.)
+* :ghpull:`18621`: Fix singleshot timers in wx.
+* :ghpull:`18607`: Backport PR #18604 on branch v3.3.x (Update test image to fix Ghostscript 9.53.)
+* :ghpull:`18604`: Update test image to fix Ghostscript 9.53.
+* :ghpull:`18584`: Fix setting 0-timeout timer with Tornado.
+* :ghpull:`18550`: backport pr 18549
+* :ghpull:`18545`: Backport PR #18540 on branch v3.3.x (Call to ExitStack.push should have been ExitStack.callback.)
+* :ghpull:`18549`: FIX: unit-convert pcolorargs before interpolating
+* :ghpull:`18540`: Call to ExitStack.push should have been ExitStack.callback.
+* :ghpull:`18533`: Correctly remove support for \stackrel.
+* :ghpull:`18509`: Backport PR #18505 on branch v3.3.x (Fix depth shading when edge/facecolor is none.)
+* :ghpull:`18505`: Fix depth shading when edge/facecolor is none.
+* :ghpull:`18504`: Backport PR #18500 on branch v3.3.x (BUG: Fix all-masked imshow)
+* :ghpull:`18500`: BUG: Fix all-masked imshow
+* :ghpull:`18476`: CI: skip qt, cairo, pygobject related installs on OSX on travis
+* :ghpull:`18134`: Build on xcode9
+
+Issues (14):
+
+* :ghissue:`18885`: 3D Scatter Plot with Colorbar is not saved correctly with savefig
+* :ghissue:`18922`: pyplot.xticks(): Font property specification is not effective except 1st tick label.
+* :ghissue:`18481`: "%matplotlib notebook" not working in firefox with matplotlib 3.3.1
+* :ghissue:`18595`: Getting internal "MatplotlibDeprecationWarning: shading='flat' ..."
+* :ghissue:`18743`: from mpl 3.2.2 to 3.3.0 enormous increase in creation time
+* :ghissue:`18317`: pcolormesh: shading='nearest' and non-monotonic coordinates
+* :ghissue:`18758`: Using Axis.grid(visible=True) results in TypeError for multiple values for keyword argument
+* :ghissue:`18638`: ``matplotlib>=3.3.2`` breaks ``ipywidgets.interact``
+* :ghissue:`18337`: Error installing matplotlib-3.3.1 using pip due to old version of certifi on conda environment
+* :ghissue:`18620`: wx backend assertion error with fig.canvas.timer.start()
+* :ghissue:`18551`: test_transparent_markers[pdf] is broken on v3.3.x Travis macOS
+* :ghissue:`18580`: Animation freezes in Jupyter notebook
+* :ghissue:`18547`: pcolormesh x-axis with datetime broken for nearest shading
+* :ghissue:`18539`: Error in Axes.redraw_in_frame in use of ExitStack: push() takes 2 positional arguments but 3 were given
Previous GitHub Stats
diff --git a/doc/users/prev_whats_new/github_stats_3.3.2.rst b/doc/users/prev_whats_new/github_stats_3.3.2.rst
new file mode 100644
index 000000000000..8f9bb9a6eceb
--- /dev/null
+++ b/doc/users/prev_whats_new/github_stats_3.3.2.rst
@@ -0,0 +1,89 @@
+.. _github-stats-3-3-2:
+
+GitHub Stats for Matplotlib 3.3.2
+=================================
+
+GitHub stats for 2020/08/14 - 2020/09/15 (tag: v3.3.1)
+
+These lists are automatically generated, and may be incomplete or contain duplicates.
+
+We closed 15 issues and merged 39 pull requests.
+The full list can be seen `on GitHub `__
+
+The following 13 authors contributed 61 commits.
+
+* Antony Lee
+* Bruno Beltran
+* David Stansby
+* David Young
+* Elliott Sales de Andrade
+* Greg Lucas
+* Jody Klymak
+* johnthagen
+* Jouni K. Seppänen
+* Richard Sheridan
+* Ryan May
+* Thomas A Caswell
+* Tim Hoffmann
+
+GitHub issues and pull requests:
+
+Pull Requests (39):
+
+* :ghpull:`18488`: Backport PR #18483 on branch v3.3.x (DOC: reword non-monotonic cell center warning)
+* :ghpull:`18483`: DOC: reword non-monotonic cell center warning
+* :ghpull:`18485`: Backport PR #18475 on branch v3.3.x (BF: ensure exception caught if no kpeswitch)
+* :ghpull:`18482`: Backport PR #18398 on branch v3.3.x (Warn on non-increasing/decreasing pcolor coords)
+* :ghpull:`18484`: Backport PR #18458: Fix huge imshow range
+* :ghpull:`18475`: BF: ensure exception caught if no kpeswitch
+* :ghpull:`18458`: Fix huge imshow range
+* :ghpull:`18398`: Warn on non-increasing/decreasing pcolor coords
+* :ghpull:`18479`: Nbagg backports
+* :ghpull:`18454`: nbagg: Use OutputArea event to trigger figure close.
+* :ghpull:`18469`: Backport PR #18464 on branch v3.3.x (Remove extra stickies in barstacked histogram.)
+* :ghpull:`18464`: Remove extra stickies in barstacked histogram.
+* :ghpull:`18459`: Backport PR #18393 on branch v3.3.x (Fix Axis scale on twinned Axes.)
+* :ghpull:`18393`: Fix Axis scale on twinned Axes.
+* :ghpull:`18441`: Backport PR #18395: TkAgg bugfix: deselect buttons that are not the current _Mode
+* :ghpull:`18395`: TkAgg bugfix: deselect buttons that are not the current _Mode
+* :ghpull:`18380`: Backport PR #18374 on branch v3.3.x (FIX: make _reshape_2D accept pandas df with string indices)
+* :ghpull:`18374`: FIX: make _reshape_2D accept pandas df with string indices
+* :ghpull:`18376`: Backport PR #18298 on branch v3.3.x (Include license files in built distribution)
+* :ghpull:`18375`: Backport PR #18293 on branch v3.3.x (Fix scatter3d color/linewidth re-projection)
+* :ghpull:`18298`: Include license files in built distribution
+* :ghpull:`18293`: Fix scatter3d color/linewidth re-projection
+* :ghpull:`18361`: nbagg: Store DPI ratio on figure instead of window.
+* :ghpull:`18354`: Backport PR #18352 on branch v3.3.x (Avoid triggering backend resolution during qt initial import.)
+* :ghpull:`18352`: Avoid triggering backend resolution during qt initial import.
+* :ghpull:`18335`: Backport PR #18322 on branch v3.3.x (Disable FH4 so that we don't require VCRUNTIME140_1.dll.)
+* :ghpull:`18322`: Disable FH4 so that we don't require VCRUNTIME140_1.dll.
+* :ghpull:`18333`: Backport PR #18328 on branch v3.3.x (Add missing check for None in Qt toolmanager.)
+* :ghpull:`18328`: Add missing check for None in Qt toolmanager.
+* :ghpull:`18309`: Backport PR #18304 on branch v3.3.x (Fix canvas redraws during motion in figures with a Button or TextBox)
+* :ghpull:`18304`: Fix canvas redraws during motion in figures with a Button or TextBox
+* :ghpull:`18297`: Backport PR #18288 on branch v3.3.x (FIX: check if axes is off page before repositioning title)
+* :ghpull:`18288`: FIX: check if axes is off page before repositioning title
+* :ghpull:`18269`: Backport PR #18266 on branch v3.3.x (Fix Path.get_extents for empty paths.)
+* :ghpull:`18266`: Fix Path.get_extents for empty paths.
+* :ghpull:`18263`: Backport PR #18260 on branch v3.3.x (Add parent widget to IntVar)
+* :ghpull:`18260`: Add parent widget to IntVar
+* :ghpull:`18253`: Backport PR #18245 on branch v3.3.x
+* :ghpull:`18245`: MNT: do a better job guessing the GUI framework in use
+
+Issues (15):
+
+* :ghissue:`18415`: imshow with LogNorm crashes with certain inputs
+* :ghissue:`18447`: nbagg: Closing a figure from the notebook does not close the python figure
+* :ghissue:`18470`: interactive plots slow with matplotlib 3.3.1
+* :ghissue:`18457`: Incorrect log y-scale for histogram with partitioned and barstacked data
+* :ghissue:`18385`: twinx not respecting log-scale
+* :ghissue:`18371`: Plotting a pandas DataFrame with string MultiIndex
+* :ghissue:`18296`: LICENSE file(s) not included in published PyPI package
+* :ghissue:`18287`: scatter3D assigns wrong color to points for some plot orientations
+* :ghissue:`18292`: ImportError: DLL load failed with Matplotlib 3.3.1 on Windows
+* :ghissue:`18327`: Tool Manager: adding buttons to toolbar fails with matplotlib version 3.3.1 using Qt backend
+* :ghissue:`18324`: Poor UI responsiveness of 3.3.1 compared with 3.2.2 for interactive mode UI using widgets
+* :ghissue:`18303`: Canvas redraws during any motion when Button is present
+* :ghissue:`18283`: Automatic title placement wrong if parent axes is off the page
+* :ghissue:`18254`: scatter(..., marker='') raises on drawing with mpl3.3.1
+* :ghissue:`18259`: New IntVar needs a parent widget
diff --git a/lib/matplotlib/__init__.py b/lib/matplotlib/__init__.py
index 340eaeb83910..1c47973f1505 100644
--- a/lib/matplotlib/__init__.py
+++ b/lib/matplotlib/__init__.py
@@ -762,7 +762,11 @@ def is_url(https://github.com/filename):
@functools.lru_cache()
def _get_ssl_context():
- import certifi
+ try:
+ import certifi
+ except ImportError:
+ _log.debug("Could not import certifi.")
+ return None
import ssl
return ssl.create_default_context(cafile=certifi.where())
@@ -771,7 +775,12 @@ def _get_ssl_context():
def _open_file_or_url(https://github.com/fname):
if not isinstance(fname, Path) and is_url(https://github.com/fname):
import urllib.request
- with urllib.request.urlopen(fname, context=_get_ssl_context()) as f:
+ ssl_ctx = _get_ssl_context()
+ if ssl_ctx is None:
+ _log.debug(
+ "Could not get certifi ssl context, https may not work."
+ )
+ with urllib.request.urlopen(fname, context=ssl_ctx) as f:
yield (line.decode('utf-8') for line in f)
else:
fname = os.path.expanduser(fname)
diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py
index 7018bbb3626d..72ca0e6266d4 100644
--- a/lib/matplotlib/axes/_axes.py
+++ b/lib/matplotlib/axes/_axes.py
@@ -5535,8 +5535,7 @@ def imshow(self, X, cmap=None, norm=None, aspect=None,
self.add_image(im)
return im
- @staticmethod
- def _pcolorargs(funcname, *args, shading='flat'):
+ def _pcolorargs(self, funcname, *args, shading='flat', **kwargs):
# - create X and Y if not present;
# - reshape X and Y as needed if they are 1-D;
# - check for proper sizes based on `shading` kwarg;
@@ -5567,6 +5566,10 @@ def _pcolorargs(funcname, *args, shading='flat'):
# Check x and y for bad data...
C = np.asanyarray(args[2])
X, Y = [cbook.safe_masked_invalid(a) for a in args[:2]]
+ # unit conversion allows e.g. datetime objects as axis values
+ self._process_unit_info(xdata=X, ydata=Y, kwargs=kwargs)
+ X = self.convert_xunits(X)
+ Y = self.convert_yunits(Y)
if funcname == 'pcolormesh':
if np.ma.is_masked(X) or np.ma.is_masked(Y):
raise ValueError(
@@ -5815,14 +5818,10 @@ def pcolor(self, *args, shading=None, alpha=None, norm=None, cmap=None,
if shading is None:
shading = rcParams['pcolor.shading']
shading = shading.lower()
- X, Y, C, shading = self._pcolorargs('pcolor', *args, shading=shading)
+ X, Y, C, shading = self._pcolorargs('pcolor', *args, shading=shading,
+ kwargs=kwargs)
Ny, Nx = X.shape
- # unit conversion allows e.g. datetime objects as axis values
- self._process_unit_info(xdata=X, ydata=Y, kwargs=kwargs)
- X = self.convert_xunits(X)
- Y = self.convert_yunits(Y)
-
# convert to MA, if necessary.
C = ma.asarray(C)
X = ma.asarray(X)
@@ -6091,14 +6090,10 @@ def pcolormesh(self, *args, alpha=None, norm=None, cmap=None, vmin=None,
kwargs.setdefault('edgecolors', 'None')
X, Y, C, shading = self._pcolorargs('pcolormesh', *args,
- shading=shading)
+ shading=shading, kwargs=kwargs)
Ny, Nx = X.shape
X = X.ravel()
Y = Y.ravel()
- # unit conversion allows e.g. datetime objects as axis values
- self._process_unit_info(xdata=X, ydata=Y, kwargs=kwargs)
- X = self.convert_xunits(X)
- Y = self.convert_yunits(Y)
# convert to one dimensional arrays
C = C.ravel()
diff --git a/lib/matplotlib/axes/_base.py b/lib/matplotlib/axes/_base.py
index 404379adcecc..278dab735e6f 100644
--- a/lib/matplotlib/axes/_base.py
+++ b/lib/matplotlib/axes/_base.py
@@ -2774,7 +2774,7 @@ def redraw_in_frame(self):
with ExitStack() as stack:
for artist in [*self._get_axis_list(),
self.title, self._left_title, self._right_title]:
- stack.push(artist.set_visible, artist.get_visible())
+ stack.callback(artist.set_visible, artist.get_visible())
artist.set_visible(False)
self.draw(self.figure._cachedRenderer)
@@ -2885,8 +2885,6 @@ def grid(self, b=None, which='major', axis='both', **kwargs):
use `.set_axisbelow` or, for more control, call the
`~.Artist.set_zorder` method of each axis.
"""
- if len(kwargs):
- b = True
cbook._check_in_list(['x', 'y', 'both'], axis=axis)
if axis in ['x', 'both']:
self.xaxis.grid(b, which=which, **kwargs)
diff --git a/lib/matplotlib/axis.py b/lib/matplotlib/axis.py
index f970a4452660..10d132f03694 100644
--- a/lib/matplotlib/axis.py
+++ b/lib/matplotlib/axis.py
@@ -778,10 +778,10 @@ def cla(self):
self.callbacks = cbook.CallbackRegistry()
# whether the grids are on
- self._gridOnMajor = (
+ self._major_tick_kw['gridOn'] = (
mpl.rcParams['axes.grid'] and
mpl.rcParams['axes.grid.which'] in ('both', 'major'))
- self._gridOnMinor = (
+ self._minor_tick_kw['gridOn'] = (
mpl.rcParams['axes.grid'] and
mpl.rcParams['axes.grid.which'] in ('both', 'minor'))
@@ -1381,7 +1381,6 @@ def get_major_ticks(self, numticks=None):
# Update the new tick label properties from the old.
tick = self._get_tick(major=True)
self.majorTicks.append(tick)
- tick.gridline.set_visible(self._gridOnMajor)
self._copy_tick_props(self.majorTicks[0], tick)
return self.majorTicks[:numticks]
@@ -1395,7 +1394,6 @@ def get_minor_ticks(self, numticks=None):
# Update the new tick label properties from the old.
tick = self._get_tick(major=False)
self.minorTicks.append(tick)
- tick.gridline.set_visible(self._gridOnMinor)
self._copy_tick_props(self.minorTicks[0], tick)
return self.minorTicks[:numticks]
@@ -1420,32 +1418,37 @@ def grid(self, b=None, which='major', **kwargs):
Define the line properties of the grid, e.g.::
grid(color='r', linestyle='-', linewidth=2)
-
"""
- if len(kwargs):
- if not b and b is not None: # something false-like but not None
+ if b is not None:
+ if 'visible' in kwargs and bool(b) != bool(kwargs['visible']):
+ raise ValueError(
+ "'b' and 'visible' specify inconsistent grid visibilities")
+ if kwargs and not b: # something false-like but not None
cbook._warn_external('First parameter to grid() is false, '
'but line properties are supplied. The '
'grid will be enabled.')
- b = True
+ b = True
which = which.lower()
cbook._check_in_list(['major', 'minor', 'both'], which=which)
gridkw = {'grid_' + item[0]: item[1] for item in kwargs.items()}
+ if 'grid_visible' in gridkw:
+ forced_visibility = True
+ gridkw['gridOn'] = gridkw.pop('grid_visible')
+ else:
+ forced_visibility = False
if which in ['minor', 'both']:
- if b is None:
- self._gridOnMinor = not self._gridOnMinor
- else:
- self._gridOnMinor = b
- self.set_tick_params(which='minor', gridOn=self._gridOnMinor,
- **gridkw)
+ if b is None and not forced_visibility:
+ gridkw['gridOn'] = not self._minor_tick_kw['gridOn']
+ elif b is not None:
+ gridkw['gridOn'] = b
+ self.set_tick_params(which='minor', **gridkw)
if which in ['major', 'both']:
- if b is None:
- self._gridOnMajor = not self._gridOnMajor
- else:
- self._gridOnMajor = b
- self.set_tick_params(which='major', gridOn=self._gridOnMajor,
- **gridkw)
+ if b is None and not forced_visibility:
+ gridkw['gridOn'] = not self._major_tick_kw['gridOn']
+ elif b is not None:
+ gridkw['gridOn'] = b
+ self.set_tick_params(which='major', **gridkw)
self.stale = True
def update_units(self, data):
diff --git a/lib/matplotlib/backends/backend_webagg_core.py b/lib/matplotlib/backends/backend_webagg_core.py
index 2155381fbd77..5377f3dc266b 100644
--- a/lib/matplotlib/backends/backend_webagg_core.py
+++ b/lib/matplotlib/backends/backend_webagg_core.py
@@ -517,7 +517,7 @@ def _timer_start(self):
else:
self._timer = tornado.ioloop.PeriodicCallback(
self._on_timer,
- self.interval)
+ max(self.interval, 1e-6))
self._timer.start()
def _timer_stop(self):
diff --git a/lib/matplotlib/backends/backend_wx.py b/lib/matplotlib/backends/backend_wx.py
index 460057cc30e6..0b94f2116ada 100644
--- a/lib/matplotlib/backends/backend_wx.py
+++ b/lib/matplotlib/backends/backend_wx.py
@@ -87,9 +87,6 @@ def _timer_set_interval(self):
if self._timer.IsRunning():
self._timer_start() # Restart with new interval.
- def _timer_set_single_shot(self):
- self._timer.Start()
-
class RendererWx(RendererBase):
"""
diff --git a/lib/matplotlib/backends/web_backend/js/mpl.js b/lib/matplotlib/backends/web_backend/js/mpl.js
index 9e7959ec30e2..a3a8f7abc54b 100644
--- a/lib/matplotlib/backends/web_backend/js/mpl.js
+++ b/lib/matplotlib/backends/web_backend/js/mpl.js
@@ -169,7 +169,17 @@ mpl.figure.prototype._init_canvas = function () {
'box-sizing: content-box; position: absolute; left: 0; top: 0; z-index: 1;'
);
- var resizeObserver = new ResizeObserver(function (entries) {
+ // 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) {
var nentries = entries.length;
for (var i = 0; i < nentries; i++) {
var entry = entries[i];
@@ -222,7 +232,7 @@ mpl.figure.prototype._init_canvas = function () {
}
}
});
- resizeObserver.observe(canvas_div);
+ this.resizeObserverInstance.observe(canvas_div);
function on_mouse_event_closure(name) {
return function (event) {
@@ -669,3 +679,7 @@ 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/js/nbagg_mpl.js b/lib/matplotlib/backends/web_backend/js/nbagg_mpl.js
index 0f538979d19d..9c4ff87b5f7d 100644
--- a/lib/matplotlib/backends/web_backend/js/nbagg_mpl.js
+++ b/lib/matplotlib/backends/web_backend/js/nbagg_mpl.js
@@ -48,7 +48,7 @@ mpl.mpl_figure_comm = function (comm, msg) {
console.error('Failed to find cell for figure', id, fig);
return;
}
- fig.cell_info[0].output_area.element.one(
+ fig.cell_info[0].output_area.element.on(
'cleared',
{ fig: fig },
fig._remove_fig_handler
@@ -61,6 +61,7 @@ mpl.figure.prototype.handle_close = function (fig, msg) {
'cleared',
fig._remove_fig_handler
);
+ fig.resizeObserverInstance.unobserve(fig.canvas_div);
// Update the output cell to use the data from the current canvas.
fig.push_to_output();
@@ -181,6 +182,10 @@ mpl.figure.prototype._init_toolbar = function () {
mpl.figure.prototype._remove_fig_handler = function (event) {
var fig = event.data.fig;
+ if (event.target !== this) {
+ // Ignore bubbled events from children.
+ return;
+ }
fig.close_ws(fig, {});
};
diff --git a/lib/matplotlib/backends/web_backend/package.json b/lib/matplotlib/backends/web_backend/package.json
index e2a4009a971b..95bd8fdf54e6 100644
--- a/lib/matplotlib/backends/web_backend/package.json
+++ b/lib/matplotlib/backends/web_backend/package.json
@@ -11,5 +11,8 @@
"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/colorbar.py b/lib/matplotlib/colorbar.py
index 0dc84edc2acc..4b2f86003eaa 100644
--- a/lib/matplotlib/colorbar.py
+++ b/lib/matplotlib/colorbar.py
@@ -788,6 +788,9 @@ def _add_solids(self, X, Y, C):
Draw the colors using `~.axes.Axes.pcolormesh`;
optionally add separators.
"""
+ if C.shape[0] == Y.shape[0]:
+ # trim the last one to be compatible with old behavior.
+ C = C[:-1]
if self.orientation == 'vertical':
args = (X, Y, C)
else:
diff --git a/lib/matplotlib/dates.py b/lib/matplotlib/dates.py
index 4b56f07b9525..4a2e7374efec 100644
--- a/lib/matplotlib/dates.py
+++ b/lib/matplotlib/dates.py
@@ -285,25 +285,6 @@ def get_epoch():
return _epoch
-def _to_ordinalf(dt):
- """
- Convert :mod:`datetime` or :mod:`date` to the Gregorian date as UTC float
- days, preserving hours, minutes, seconds and microseconds. Return value
- is a `float`.
- """
- # Convert to UTC
- tzi = getattr(dt, 'tzinfo', None)
- if tzi is not None:
- dt = dt.astimezone(UTC)
- dt = dt.replace(tzinfo=None)
- dt64 = np.datetime64(dt)
- return _dt64_to_ordinalf(dt64)
-
-
-# a version of _to_ordinalf that can operate on numpy arrays
-_to_ordinalf_np_vectorized = np.vectorize(_to_ordinalf)
-
-
def _dt64_to_ordinalf(d):
"""
Convert `numpy.datetime64` or an ndarray of those types to Gregorian
@@ -428,20 +409,29 @@ def date2num(d):
if hasattr(d, "values"):
# this unpacks pandas series or dataframes...
d = d.values
- if not np.iterable(d):
- if (isinstance(d, np.datetime64) or
- (isinstance(d, np.ndarray) and
- np.issubdtype(d.dtype, np.datetime64))):
- return _dt64_to_ordinalf(d)
- return _to_ordinalf(d)
- else:
- d = np.asarray(d)
- if np.issubdtype(d.dtype, np.datetime64):
- return _dt64_to_ordinalf(d)
+ # make an iterable, but save state to unpack later:
+ iterable = np.iterable(d)
+ if not iterable:
+ d = [d]
+
+ d = np.asarray(d)
+ # convert to datetime64 arrays, if not already:
+ if not np.issubdtype(d.dtype, np.datetime64):
+ # datetime arrays
if not d.size:
+ # deals with an empty array...
return d
- return _to_ordinalf_np_vectorized(d)
+ tzi = getattr(d[0], 'tzinfo', None)
+ if tzi is not None:
+ # make datetime naive:
+ d = [dt.astimezone(UTC).replace(tzinfo=None) for dt in d]
+ d = np.asarray(d)
+ d = d.astype('datetime64[us]')
+
+ d = _dt64_to_ordinalf(d)
+
+ return d if iterable else d[0]
def julian2num(j):
diff --git a/lib/matplotlib/image.py b/lib/matplotlib/image.py
index 14866cc1012c..7d15e9f1c396 100644
--- a/lib/matplotlib/image.py
+++ b/lib/matplotlib/image.py
@@ -474,8 +474,10 @@ def _make_image(self, A, in_bbox, out_bbox, clip_bbox, magnification=1.0,
# do not run the vmin/vmax through the same pipeline we can
# have values close or equal to the boundaries end up on the
# wrong side.
- vrange = np.array([self.norm.vmin, self.norm.vmax],
- dtype=scaled_dtype)
+ vmin, vmax = self.norm.vmin, self.norm.vmax
+ if vmin is np.ma.masked:
+ vmin, vmax = a_min, a_max
+ vrange = np.array([vmin, vmax], dtype=scaled_dtype)
A_scaled -= a_min
vrange -= a_min
@@ -1479,8 +1481,12 @@ def imread(fname, format=None):
if len(parsed.scheme) > 1: # Pillow doesn't handle URLs directly.
# hide imports to speed initial import on systems with slow linkers
from urllib import request
- with request.urlopen(fname,
- context=mpl._get_ssl_context()) as response:
+ ssl_ctx = mpl._get_ssl_context()
+ if ssl_ctx is None:
+ _log.debug(
+ "Could not get certifi ssl context, https may not work."
+ )
+ with request.urlopen(fname, context=ssl_ctx) as response:
import io
try:
response.seek(0)
diff --git a/lib/matplotlib/mathtext.py b/lib/matplotlib/mathtext.py
index 1b46fc6dcd7d..d174aaf556f3 100644
--- a/lib/matplotlib/mathtext.py
+++ b/lib/matplotlib/mathtext.py
@@ -2396,7 +2396,6 @@ def __init__(self):
p.accentprefixed = Forward()
p.space = Forward()
p.sqrt = Forward()
- p.stackrel = Forward()
p.start_group = Forward()
p.subsuper = Forward()
p.subsuperop = Forward()
@@ -2481,12 +2480,6 @@ def __init__(self):
| Error(r"Expected \dfrac{num}{den}"))
)
- p.stackrel <<= Group(
- Suppress(Literal(r"\stackrel"))
- - ((p.required_group + p.required_group)
- | Error(r"Expected \stackrel{num}{den}"))
- )
-
p.binom <<= Group(
Suppress(Literal(r"\binom"))
- ((p.required_group + p.required_group)
@@ -2543,7 +2536,6 @@ def __init__(self):
| p.group
| p.frac
| p.dfrac
- | p.stackrel
| p.binom
| p.genfrac
| p.sqrt
diff --git a/lib/matplotlib/tests/baseline_images/test_axes/transparent_markers.pdf b/lib/matplotlib/tests/baseline_images/test_axes/transparent_markers.pdf
index d1245169e994..305bcb90ab99 100644
Binary files a/lib/matplotlib/tests/baseline_images/test_axes/transparent_markers.pdf and b/lib/matplotlib/tests/baseline_images/test_axes/transparent_markers.pdf differ
diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py
index 41df0f502347..57beec025aa8 100644
--- a/lib/matplotlib/tests/test_axes.py
+++ b/lib/matplotlib/tests/test_axes.py
@@ -1188,6 +1188,22 @@ def test_pcolornearest(fig_test, fig_ref):
ax.pcolormesh(x2, y2, Z, shading='nearest')
+@check_figures_equal(extensions=["png"])
+def test_pcolornearestunits(fig_test, fig_ref):
+ ax = fig_test.subplots()
+ x = [datetime.datetime.fromtimestamp(x * 3600) for x in range(10)]
+ y = np.arange(0, 3)
+ np.random.seed(19680801)
+ Z = np.random.randn(2, 9)
+ ax.pcolormesh(x, y, Z, shading='flat')
+
+ ax = fig_ref.subplots()
+ # specify the centers
+ x2 = [datetime.datetime.fromtimestamp((x + 0.5) * 3600) for x in range(9)]
+ y2 = y[:-1] + np.diff(y) / 2
+ ax.pcolormesh(x2, y2, Z, shading='nearest')
+
+
@check_figures_equal(extensions=["png"])
def test_pcolordropdata(fig_test, fig_ref):
ax = fig_test.subplots()
@@ -4294,26 +4310,35 @@ def test_twin_spines_on_top():
ax2.fill_between("i", "j", color='#7FC97F', alpha=.5, data=data)
-def test_rcparam_grid_minor():
- orig_grid = matplotlib.rcParams['axes.grid']
- orig_locator = matplotlib.rcParams['axes.grid.which']
-
- matplotlib.rcParams['axes.grid'] = True
-
- values = (
- (('both'), (True, True)),
- (('major'), (True, False)),
- (('minor'), (False, True))
- )
+@pytest.mark.parametrize("grid_which, major_visible, minor_visible", [
+ ("both", True, True),
+ ("major", True, False),
+ ("minor", False, True),
+])
+def test_rcparam_grid_minor(grid_which, major_visible, minor_visible):
+ mpl.rcParams.update({"axes.grid": True, "axes.grid.which": grid_which})
+ fig, ax = plt.subplots()
+ fig.canvas.draw()
+ assert all(tick.gridline.get_visible() == major_visible
+ for tick in ax.xaxis.majorTicks)
+ assert all(tick.gridline.get_visible() == minor_visible
+ for tick in ax.xaxis.minorTicks)
- for locator, result in values:
- matplotlib.rcParams['axes.grid.which'] = locator
- fig = plt.figure()
- ax = fig.add_subplot(1, 1, 1)
- assert (ax.xaxis._gridOnMajor, ax.xaxis._gridOnMinor) == result
- matplotlib.rcParams['axes.grid'] = orig_grid
- matplotlib.rcParams['axes.grid.which'] = orig_locator
+def test_grid():
+ fig, ax = plt.subplots()
+ ax.grid()
+ fig.canvas.draw()
+ assert ax.xaxis.majorTicks[0].gridline.get_visible()
+ ax.grid(visible=False)
+ fig.canvas.draw()
+ assert not ax.xaxis.majorTicks[0].gridline.get_visible()
+ ax.grid(visible=True)
+ fig.canvas.draw()
+ assert ax.xaxis.majorTicks[0].gridline.get_visible()
+ ax.grid()
+ fig.canvas.draw()
+ assert not ax.xaxis.majorTicks[0].gridline.get_visible()
def test_vline_limit():
@@ -6350,6 +6375,13 @@ def test_bbox_aspect_axes_init():
assert_allclose(sizes, sizes[0])
+def test_redraw_in_frame():
+ fig, ax = plt.subplots(1, 1)
+ ax.plot([1, 2, 3])
+ fig.canvas.draw()
+ ax.redraw_in_frame()
+
+
def test_invisible_axes():
# invisible axes should not respond to events...
fig, ax = plt.subplots()
diff --git a/lib/matplotlib/tests/test_image.py b/lib/matplotlib/tests/test_image.py
index 87e3c121d4c0..6ffb2e2d0554 100644
--- a/lib/matplotlib/tests/test_image.py
+++ b/lib/matplotlib/tests/test_image.py
@@ -846,6 +846,14 @@ def test_mask_image():
ax2.imshow(A, interpolation='nearest')
+def test_mask_image_all():
+ # Test behavior with an image that is entirely masked does not warn
+ data = np.full((2, 2), np.nan)
+ fig, ax = plt.subplots()
+ ax.imshow(data)
+ fig.canvas.draw_idle() # would emit a warning
+
+
@image_comparison(['imshow_endianess.png'], remove_text=True)
def test_imshow_endianess():
x = np.arange(10)
diff --git a/lib/matplotlib/tests/test_mathtext.py b/lib/matplotlib/tests/test_mathtext.py
index 6bc84b19f1eb..91a72a648441 100644
--- a/lib/matplotlib/tests/test_mathtext.py
+++ b/lib/matplotlib/tests/test_mathtext.py
@@ -214,8 +214,6 @@ def test_fontinfo():
(r'$\hspace{foo}$', r'Expected \hspace{n}'),
(r'$\frac$', r'Expected \frac{num}{den}'),
(r'$\frac{}{}$', r'Expected \frac{num}{den}'),
- (r'$\stackrel$', r'Expected \stackrel{num}{den}'),
- (r'$\stackrel{}{}$', r'Expected \stackrel{num}{den}'),
(r'$\binom$', r'Expected \binom{num}{den}'),
(r'$\binom{}{}$', r'Expected \binom{num}{den}'),
(r'$\genfrac$',
@@ -238,8 +236,6 @@ def test_fontinfo():
'hspace with invalid value',
'frac without parameters',
'frac with empty parameters',
- 'stackrel without parameters',
- 'stackrel with empty parameters',
'binom without parameters',
'binom with empty parameters',
'genfrac without parameters',
diff --git a/lib/matplotlib/tests/test_text.py b/lib/matplotlib/tests/test_text.py
index 70855e6d879f..8d91b1318d88 100644
--- a/lib/matplotlib/tests/test_text.py
+++ b/lib/matplotlib/tests/test_text.py
@@ -8,9 +8,11 @@
import matplotlib as mpl
from matplotlib.backend_bases import MouseEvent
+from matplotlib.font_manager import FontProperties
import matplotlib.patches as mpatches
import matplotlib.pyplot as plt
from matplotlib.testing.decorators import check_figures_equal, image_comparison
+from matplotlib.text import Text
needs_usetex = pytest.mark.skipif(
@@ -689,3 +691,13 @@ def test_fontproperties_kwarg_precedence():
text2 = plt.ylabel("counts", size=40.0, fontproperties='Times New Roman')
assert text1.get_size() == 40.0
assert text2.get_size() == 40.0
+
+
+def test_update_mutate_input():
+ inp = dict(fontproperties=FontProperties(weight="bold"),
+ bbox=None)
+ cache = dict(inp)
+ t = Text()
+ t.update(inp)
+ assert inp['fontproperties'] == cache['fontproperties']
+ assert inp['bbox'] == cache['bbox']
diff --git a/lib/matplotlib/tests/test_ticker.py b/lib/matplotlib/tests/test_ticker.py
index 611b5cf968d8..9033e30d4858 100644
--- a/lib/matplotlib/tests/test_ticker.py
+++ b/lib/matplotlib/tests/test_ticker.py
@@ -1347,3 +1347,13 @@ def test_bad_locator_subs(sub):
ll = mticker.LogLocator()
with pytest.raises(ValueError):
ll.subs(sub)
+
+
+@pytest.mark.parametrize('numticks', [1, 2, 3, 9])
+@pytest.mark.style('default')
+def test_small_range_loglocator(numticks):
+ ll = mticker.LogLocator()
+ ll.set_params(numticks=numticks)
+ for top in [5, 7, 9, 11, 15, 50, 100, 1000]:
+ ticks = ll.tick_values(.5, top)
+ assert (np.diff(np.log10(ll.tick_values(6, 150))) == 1).all()
diff --git a/lib/matplotlib/text.py b/lib/matplotlib/text.py
index 3b30a4cfb502..efe4450f4fba 100644
--- a/lib/matplotlib/text.py
+++ b/lib/matplotlib/text.py
@@ -167,6 +167,8 @@ def __init__(self,
def update(self, kwargs):
# docstring inherited
+ # make a copy so we do not mutate user input!
+ kwargs = dict(kwargs)
sentinel = object() # bbox can be None, so use another sentinel.
# Update fontproperties first, as it has lowest priority.
fontproperties = kwargs.pop("fontproperties", sentinel)
diff --git a/lib/matplotlib/ticker.py b/lib/matplotlib/ticker.py
index ced002157637..67931617423f 100644
--- a/lib/matplotlib/ticker.py
+++ b/lib/matplotlib/ticker.py
@@ -2500,6 +2500,13 @@ def tick_values(self, vmin, vmax):
if mpl.rcParams['_internal.classic_mode'] else
(numdec + 1) // numticks + 1)
+ # if we have decided that the stride is as big or bigger than
+ # the range, clip the stride back to the available range - 1
+ # with a floor of 1. This prevents getting axis with only 1 tick
+ # visible.
+ if stride >= numdec:
+ stride = max(1, numdec - 1)
+
# Does subs include anything other than 1? Essentially a hack to know
# whether we're a major or a minor locator.
have_subs = len(subs) > 1 or (len(subs) == 1 and subs[0] != 1.0)
diff --git a/lib/mpl_toolkits/axisartist/axislines.py b/lib/mpl_toolkits/axisartist/axislines.py
index db89bd434eb8..d3e32477adf2 100644
--- a/lib/mpl_toolkits/axisartist/axislines.py
+++ b/lib/mpl_toolkits/axisartist/axislines.py
@@ -439,9 +439,9 @@ def get_gridlines(self, which="major", axis="both"):
if axis in ["both", "y"]:
x1, x2 = self.axes.get_xlim()
locs = []
- if self.axes.yaxis._gridOnMajor:
+ if self.axes.yaxis._major_tick_kw["gridOn"]:
locs.extend(self.axes.yaxis.major.locator())
- if self.axes.yaxis._gridOnMinor:
+ if self.axes.yaxis._minor_tick_kw["gridOn"]:
locs.extend(self.axes.yaxis.minor.locator())
for y in locs:
@@ -533,17 +533,17 @@ def grid(self, b=None, which='major', axis="both", **kwargs):
"""
Toggle the gridlines, and optionally set the properties of the lines.
"""
- # their are some discrepancy between the behavior of grid in
- # axes_grid and the original mpl's grid, because axes_grid
- # explicitly set the visibility of the gridlines.
+ # There are some discrepancies in the behavior of grid() between
+ # axes_grid and Matplotlib, because axes_grid explicitly sets the
+ # visibility of the gridlines.
super().grid(b, which=which, axis=axis, **kwargs)
if not self._axisline_on:
return
if b is None:
- b = (self.axes.xaxis._gridOnMinor
- or self.axes.xaxis._gridOnMajor
- or self.axes.yaxis._gridOnMinor
- or self.axes.yaxis._gridOnMajor)
+ b = (self.axes.xaxis._minor_tick_kw["gridOn"]
+ or self.axes.xaxis._major_tick_kw["gridOn"]
+ or self.axes.yaxis._minor_tick_kw["gridOn"]
+ or self.axes.yaxis._major_tick_kw["gridOn"])
self.gridlines.set(which=which, axis=axis, visible=b)
self.gridlines.set(**kwargs)
diff --git a/lib/mpl_toolkits/mplot3d/art3d.py b/lib/mpl_toolkits/mplot3d/art3d.py
index 678cd0d7b872..1c6f9d0ba72d 100644
--- a/lib/mpl_toolkits/mplot3d/art3d.py
+++ b/lib/mpl_toolkits/mplot3d/art3d.py
@@ -264,6 +264,8 @@ def do_3d_projection(self, renderer):
"""
Project the points according to renderer matrix.
"""
+ # see _update_scalarmappable docstring for why this must be here
+ _update_scalarmappable(self)
xyslist = [
proj3d.proj_trans_points(points, renderer.M) for points in
self._segments3d]
@@ -418,6 +420,8 @@ def set_3d_properties(self, zs, zdir):
self.stale = True
def do_3d_projection(self, renderer):
+ # see _update_scalarmappable docstring for why this must be here
+ _update_scalarmappable(self)
xs, ys, zs = self._offsets3d
vxs, vys, vzs, vis = proj3d.proj_transform_clip(xs, ys, zs, renderer.M)
@@ -486,6 +490,8 @@ def set_3d_properties(self, zs, zdir):
self.stale = True
def do_3d_projection(self, renderer):
+ # see _update_scalarmappable docstring for why this must be here
+ _update_scalarmappable(self)
xs, ys, zs = self._offsets3d
vxs, vys, vzs, vis = proj3d.proj_transform_clip(xs, ys, zs, renderer.M)
@@ -528,6 +534,77 @@ def do_3d_projection(self, renderer):
return np.min(vzs) if vzs.size else np.nan
+def _update_scalarmappable(sm):
+ """
+ Update a 3D ScalarMappable.
+
+ With ScalarMappable objects if the data, colormap, or norm are
+ changed, we need to update the computed colors. This is handled
+ by the base class method update_scalarmappable. This method works
+ by, detecting if work needs to be done, and if so stashing it on
+ the ``self._facecolors`` attribute.
+
+ With 3D collections we internally sort the components so that
+ things that should be "in front" are rendered later to simulate
+ having a z-buffer (in addition to doing the projections). This is
+ handled in the ``do_3d_projection`` methods which are called from the
+ draw method of the 3D Axes. These methods:
+
+ - do the projection from 3D -> 2D
+ - internally sort based on depth
+ - stash the results of the above in the 2D analogs of state
+ - return the z-depth of the whole artist
+
+ the last step is so that we can, at the Axes level, sort the children by
+ depth.
+
+ The base `draw` method of the 2D artists unconditionally calls
+ update_scalarmappable and rely on the method's internal caching logic to
+ lazily evaluate.
+
+ These things together mean you can have the sequence of events:
+
+ - we create the artist, do the color mapping and stash the results
+ in a 3D specific state.
+ - change something about the ScalarMappable that marks it as in
+ need of an update (`ScalarMappable.changed` and friends).
+ - We call do_3d_projection and shuffle the stashed colors into the
+ 2D version of face colors
+ - the draw method calls the update_scalarmappable method which
+ overwrites our shuffled colors
+ - we get a render that is wrong
+ - if we re-render (either with a second save or implicitly via
+ tight_layout / constrained_layout / bbox_inches='tight' (ex via
+ inline's defaults)) we again shuffle the 3D colors
+ - because the CM is not marked as changed update_scalarmappable is
+ a no-op and we get a correct looking render.
+
+ This function is an internal helper to:
+
+ - sort out if we need to do the color mapping at all (has data!)
+ - sort out if update_scalarmappable is going to be a no-op
+ - copy the data over from the 2D -> 3D version
+
+ This must be called first thing in do_3d_projection to make sure that
+ the correct colors get shuffled.
+
+ Parameters
+ ----------
+ sm : ScalarMappable
+ The ScalarMappable to update and stash the 3D data from
+
+ """
+ if sm._A is None:
+ return
+ copy_state = sm._update_dict['array']
+ ret = sm.update_scalarmappable()
+ if copy_state:
+ if sm._is_filled:
+ sm._facecolor3d = sm._facecolors
+ elif sm._is_stroked:
+ sm._edgecolor3d = sm._edgecolors
+
+
def patch_collection_2d_to_3d(col, zs=0, zdir='z', depthshade=True):
"""
Convert a :class:`~matplotlib.collections.PatchCollection` into a
@@ -650,8 +727,8 @@ def set_3d_properties(self):
self.update_scalarmappable()
self._sort_zpos = None
self.set_zsort('average')
- self._facecolors3d = PolyCollection.get_facecolor(self)
- self._edgecolors3d = PolyCollection.get_edgecolor(self)
+ self._facecolor3d = PolyCollection.get_facecolor(self)
+ self._edgecolor3d = PolyCollection.get_edgecolor(self)
self._alpha3d = PolyCollection.get_alpha(self)
self.stale = True
@@ -664,17 +741,15 @@ def do_3d_projection(self, renderer):
"""
Perform the 3D projection for this object.
"""
- # FIXME: This may no longer be needed?
- if self._A is not None:
- self.update_scalarmappable()
- self._facecolors3d = self._facecolors
+ # see _update_scalarmappable docstring for why this must be here
+ _update_scalarmappable(self)
txs, tys, tzs = proj3d._proj_transform_vec(self._vec, renderer.M)
xyzlist = [(txs[sl], tys[sl], tzs[sl]) for sl in self._segslices]
# This extra fuss is to re-order face / edge colors
- cface = self._facecolors3d
- cedge = self._edgecolors3d
+ cface = self._facecolor3d
+ cedge = self._edgecolor3d
if len(cface) != len(xyzlist):
cface = cface.repeat(len(xyzlist), axis=0)
if len(cedge) != len(xyzlist):
@@ -699,8 +774,8 @@ def do_3d_projection(self, renderer):
else:
PolyCollection.set_verts(self, segments_2d, self._closed)
- if len(self._edgecolors3d) != len(cface):
- self._edgecolors2d = self._edgecolors3d
+ if len(self._edgecolor3d) != len(cface):
+ self._edgecolors2d = self._edgecolor3d
# Return zorder value
if self._sort_zpos is not None:
@@ -717,23 +792,23 @@ def do_3d_projection(self, renderer):
def set_facecolor(self, colors):
PolyCollection.set_facecolor(self, colors)
- self._facecolors3d = PolyCollection.get_facecolor(self)
+ self._facecolor3d = PolyCollection.get_facecolor(self)
def set_edgecolor(self, colors):
PolyCollection.set_edgecolor(self, colors)
- self._edgecolors3d = PolyCollection.get_edgecolor(self)
+ self._edgecolor3d = PolyCollection.get_edgecolor(self)
def set_alpha(self, alpha):
# docstring inherited
artist.Artist.set_alpha(self, alpha)
try:
- self._facecolors3d = mcolors.to_rgba_array(
- self._facecolors3d, self._alpha)
+ self._facecolor3d = mcolors.to_rgba_array(
+ self._facecolor3d, self._alpha)
except (AttributeError, TypeError, IndexError):
pass
try:
self._edgecolors = mcolors.to_rgba_array(
- self._edgecolors3d, self._alpha)
+ self._edgecolor3d, self._alpha)
except (AttributeError, TypeError, IndexError):
pass
self.stale = True
@@ -803,7 +878,7 @@ def _zalpha(colors, zs):
# in all three dimensions. Otherwise, at certain orientations,
# the min and max zs are very close together.
# Should really normalize against the viewing depth.
- if len(zs) == 0:
+ if len(colors) == 0 or len(zs) == 0:
return np.zeros((0, 4))
norm = Normalize(min(zs), max(zs))
sats = 1 - norm(zs) * 0.7
diff --git a/lib/mpl_toolkits/tests/test_mplot3d.py b/lib/mpl_toolkits/tests/test_mplot3d.py
index 9c63dc8f9fa8..999dafdc92a9 100644
--- a/lib/mpl_toolkits/tests/test_mplot3d.py
+++ b/lib/mpl_toolkits/tests/test_mplot3d.py
@@ -106,7 +106,7 @@ def test_bar3d_lightsource():
# the top facecolors compared to the default, and that those colors are
# precisely the colors from the colormap, due to the illumination parallel
# to the z-axis.
- np.testing.assert_array_equal(color, collection._facecolors3d[1::6])
+ np.testing.assert_array_equal(color, collection._facecolor3d[1::6])
@mpl3d_image_comparison(['contour3d.png'])
@@ -237,8 +237,14 @@ def test_scatter3d():
def test_scatter3d_color():
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
+
+ # Check that 'none' color works; these two should overlay to produce the
+ # same as setting just `color`.
+ ax.scatter(np.arange(10), np.arange(10), np.arange(10),
+ facecolor='r', edgecolor='none', marker='o')
ax.scatter(np.arange(10), np.arange(10), np.arange(10),
- color='r', marker='o')
+ facecolor='none', edgecolor='r', marker='o')
+
ax.scatter(np.arange(10, 20), np.arange(10, 20), np.arange(10, 20),
color='b', marker='s')
@@ -1081,3 +1087,25 @@ def test_colorbar_pos():
fig.canvas.draw()
# check that actually on the bottom
assert cbar.ax.get_position().extents[1] < 0.2
+
+
+@pytest.mark.style('default')
+@check_figures_equal(extensions=["png"])
+def test_scalarmap_update(fig_test, fig_ref):
+
+ x, y, z = np.array((list(itertools.product(*[np.arange(0, 5, 1),
+ np.arange(0, 5, 1),
+ np.arange(0, 5, 1)])))).T
+ c = x + y
+
+ # test
+ ax_test = fig_test.add_subplot(111, projection='3d')
+ sc_test = ax_test.scatter(x, y, z, c=c, s=40, cmap='viridis')
+ # force a draw
+ fig_test.canvas.draw()
+ # mark it as "stale"
+ sc_test.changed()
+
+ # ref
+ ax_ref = fig_ref.add_subplot(111, projection='3d')
+ sc_ref = ax_ref.scatter(x, y, z, c=c, s=40, cmap='viridis')
diff --git a/setup.py b/setup.py
index 81eae826373d..6e1d19d4b85c 100644
--- a/setup.py
+++ b/setup.py
@@ -287,7 +287,6 @@ def build_extensions(self):
"numpy>=1.15",
],
install_requires=[
- "certifi>=2020.06.20",
"cycler>=0.10",
"kiwisolver>=1.0.1",
"numpy>=1.15",
diff --git a/tools/cache_zenodo_svg.py b/tools/cache_zenodo_svg.py
index b3683fe19011..27d707ae36b7 100644
--- a/tools/cache_zenodo_svg.py
+++ b/tools/cache_zenodo_svg.py
@@ -62,6 +62,7 @@ def _get_xdg_cache_dir():
if __name__ == "__main__":
data = {
+ "v3.3.2": "4030140",
"v3.3.1": "3984190",
"v3.3.0": "3948793",
"v3.2.2": "3898017",
diff --git a/tools/embed_js.py b/tools/embed_js.py
new file mode 100644
index 000000000000..571bf80238e9
--- /dev/null
+++ b/tools/embed_js.py
@@ -0,0 +1,102 @@
+"""
+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/tutorials/text/mathtext.py b/tutorials/text/mathtext.py
index 7b5b078b4fa2..709f1df4956b 100644
--- a/tutorials/text/mathtext.py
+++ b/tutorials/text/mathtext.py
@@ -97,7 +97,7 @@
.. math::
- \frac{3}{4} \binom{3}{4} \stackrel{}{}{0}{}{3}{4}
+ \frac{3}{4} \binom{3}{4} \genfrac{}{}{0}{}{3}{4}
Fractions can be arbitrarily nested::