From 733a028fe5b804ddeed60f66722e38e7adf8edd3 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Tue, 15 Sep 2020 02:44:27 -0400 Subject: [PATCH 01/27] BLD: bump branch away from tag So the tarballs from GitHub are stable. From 6ba186cfe88f78b57009830714ce6628e763a144 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Tue, 15 Sep 2020 03:02:39 -0400 Subject: [PATCH 02/27] DOC: Add Zenodo DOI for 3.3.2. --- doc/_static/zenodo_cache/4030140.svg | 35 ++++++++++++++++++++++++++++ doc/citing.rst | 3 +++ tools/cache_zenodo_svg.py | 1 + 3 files changed, 39 insertions(+) create mode 100644 doc/_static/zenodo_cache/4030140.svg 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 @@ + + + + + + + + + + + + + + + + DOI + + + DOI + + + 10.5281/zenodo.4030140 + + + 10.5281/zenodo.4030140 + + + \ No newline at end of file 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/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", From 4ddec28865b581438a31fa3a4e2c2256d628901e Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Wed, 16 Sep 2020 20:31:37 -0400 Subject: [PATCH 03/27] Backport PR #18500: BUG: Fix all-masked imshow --- lib/matplotlib/image.py | 6 ++++-- lib/matplotlib/tests/test_image.py | 8 ++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/image.py b/lib/matplotlib/image.py index 14866cc1012c..ac5706ae7cf7 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 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) From a7ea0440c2bd2c6f0c09ec1e5f5d6378f57cd395 Mon Sep 17 00:00:00 2001 From: Jody Klymak Date: Thu, 17 Sep 2020 09:13:49 -0700 Subject: [PATCH 04/27] Backport PR #18505: Fix depth shading when edge/facecolor is none. --- lib/mpl_toolkits/mplot3d/art3d.py | 2 +- lib/mpl_toolkits/tests/test_mplot3d.py | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/mpl_toolkits/mplot3d/art3d.py b/lib/mpl_toolkits/mplot3d/art3d.py index 678cd0d7b872..02a310bd92bb 100644 --- a/lib/mpl_toolkits/mplot3d/art3d.py +++ b/lib/mpl_toolkits/mplot3d/art3d.py @@ -803,7 +803,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..7d4d9d3e1c77 100644 --- a/lib/mpl_toolkits/tests/test_mplot3d.py +++ b/lib/mpl_toolkits/tests/test_mplot3d.py @@ -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') From 078ee9483b754bb22460d43a62e356149f8b7073 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Tue, 22 Sep 2020 16:00:39 -0400 Subject: [PATCH 05/27] Backport PR #18540: Call to ExitStack.push should have been ExitStack.callback. --- lib/matplotlib/axes/_base.py | 2 +- lib/matplotlib/tests/test_axes.py | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/matplotlib/axes/_base.py b/lib/matplotlib/axes/_base.py index 404379adcecc..62dee0983fdb 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) diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index 41df0f502347..4a9d1c2de57a 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -6350,6 +6350,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() From 3edd1765d98b59253eb4f7c95af871766918ea9b Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Wed, 23 Sep 2020 13:22:26 -0400 Subject: [PATCH 06/27] Merge pull request #18549 from jklymak/fix-pcolorarg-convert-before-interp FIX: unit-convert pcolorargs before interpolating --- lib/matplotlib/axes/_axes.py | 21 ++++++++------------- lib/matplotlib/tests/test_axes.py | 16 ++++++++++++++++ 2 files changed, 24 insertions(+), 13 deletions(-) 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/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index 41df0f502347..c0d2181ace62 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() From 1b4ca8fbcf24f3353877eec84f478a3f0826ba7c Mon Sep 17 00:00:00 2001 From: David Stansby Date: Tue, 29 Sep 2020 11:06:14 +0100 Subject: [PATCH 07/27] Backport PR #18604: Update test image to fix Ghostscript 9.53. --- .../test_axes/transparent_markers.pdf | Bin 2326 -> 2218 bytes 1 file changed, 0 insertions(+), 0 deletions(-) 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 d1245169e994157aab13bd152a611d4f87cd4587..305bcb90ab9932abfc07b30773a5ff2252bcfe8f 100644 GIT binary patch literal 2218 zcmai0eK=HU6z{2LSB)rrsHJotBDBohnYm^}Y78@3VHlJcjBzm&Gu*itHXrSV6_HI5 z75Uoom8}(#wpx`o^f|3swnX*VjgnezQhV=wAbNK1KX=Y=&Uw!{zxREA=NfYySQelq zm0(=m4OBG|K!5~Df0(N!;SR9Ny(x(JC z%1|~J<)bh_RR*ynVice#e{=XK3L|0w!c78^gGdBi7zKQ>JPs^ueOQJ9M5hQWr@iXU zRK1-59zf)V@q>lpP{2wVzSS*A1Pf4rrdWKk9FT+o>IN8Mu>_T3DX@4ZRthY}ENkTg zY(x?r3E*uqxd71tju8rA4>pz>vpf;LSQ^PkV6h-h-MP{qOGI>mgN1y1i40p8PX^Jf z04jxy=iuNWsG2bkSSpD|1Q>-lM-vp{tWMz%FGpQ3OBjw(1Q1!_7>5q9KoX2mz8MyW zqG14t?~Df?C`A#NA3=~EO7M0O8_?$WTsK2^x|x`LUvdDPz1;MM<9){kfsNvC8&mX) zTM~OT1k^xKulKw6U~&leWaA#m8b`*tlnW(?DNQo@>8JPIX9%hbD5wyoa!Z;-z!* z{u7b?!DG1>N?lN5$fw(4kZR@i#`IBw4Aw?AubGgzp6cq$X-9jjwqzPQ*kmv5{81zi z3ew!ZSt}#eU$?z1Zn*UNg5LYy^-q&QrcYJF*40^W-vw5BSa?6{*u&th@sC_lUOC&h zVy&*z&?)_vi<h3)AGD{uK%m;b4G1CqM~K*PiSn5Wsa5}Z73h|9-_stcy8@xyvDv?(py?JiK$6r z=^M&5lXlRD^JKjsA?mB5H{7NdSJD0|E158RfGHn__V{J7;59fVmKW~0g^W$sFr2)F%$=qU>ubi z#-?l>KsA{#W=Q{A+5gsnWPrw`=?o^?2*mdF2DXD^(BMoUk}{ZdA&d>K%9}+JF(3&c zKV1oTO3|DZ$oTmP8pbkN7QiR1i~*>M$~Nh#=r9=Bp@HKw3O~iNI@)T{u@tS(fhzE- zz~7uh7Np;uR|=pCnr2{xBtBj=Wo5EhJ=Bla>qY(e4LJ83LD5Do4d8qcq5-NlnW2R6 zZH7XrC_Q7Kc;T%aOX)HZ+m^ieNV-kx-rD0;_wC*_UYc3wUK=^w9V@svHFqgkCr+DF zF;d>L{cdyF?U@sU-3xsDFKiDB-?GhT=IZ>*p4*mq?tOpS>Nga*^A5_}=2tPnpLqZA zFzQ@qGyB-1^1~eqYhxSKW!{Y?S)SjthvYX6a|_SQId!7KBc>}ZDtPffeQT!gIehQV z*11K-Pm`X8*5y3wc>MU^#`%kC@AZA(IlML@FsEjGC~M2Oe<5A-r1{>RsPKm;{NyE@ zRYx~@hX*(1?BjwuF2#nbGJFrXQM;ly#V_t@RP#T?P?na} zw$(o(uJTmj@vrcG4^Da(m&e8}ILDoyaOOd~`f!IT)9>A5#gwomal`qqNX+&}<(b|Q zjhns7c>3#w&2%a+8!m#J9<_We|U1l>im_Bb+_xen@`60Em`^cyxqFfSs$(U=&%imX^Q z&$9ko)lW716}01xB%Gwlzi2-~ttL+A-gM)1STt?6k2PcCim_R1R#1bivOC?~?@a2y$ zkCKsB0t=1FO5yB{M9r|70PSa-$gah6lo8sdTS9Het;-_qC6VM3At5oMR@?MjNcCth zcIkU#d&dcqW6!t?sul=bI<=!KhgOWw)tTB1lY!^1y%$9D_;uv!Xkd{2SrFK~$j-{* z1Eob!C8R6IuwZhgjGYnsKduNR?q(9n0Be#bsg%5)cp?rAGb(;X*Ol From 8b25e2f38de556ec6fdfffd27518ebec8393fe6e Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Thu, 1 Oct 2020 11:18:57 -0400 Subject: [PATCH 08/27] Backport PR #18621: Fix singleshot timers in wx. --- lib/matplotlib/backends/backend_wx.py | 3 --- 1 file changed, 3 deletions(-) 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): """ From 1fa9903715751f39361484982709ce962c470b3f Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Fri, 2 Oct 2020 19:23:43 -0400 Subject: [PATCH 09/27] Backport PR #18636: BLD: certifi is not a run-time dependency --- .github/workflows/cibuildwheel.yml | 6 +++--- setup.py | 1 - 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/cibuildwheel.yml b/.github/workflows/cibuildwheel.yml index 9ee39d0c7841..462be6d5a5b7 100644 --- a/.github/workflows/cibuildwheel.yml +++ b/.github/workflows/cibuildwheel.yml @@ -42,7 +42,7 @@ jobs: CIBW_SKIP: "cp35-* 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" - name: Build wheels for CPython 3.6 @@ -52,7 +52,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 +63,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/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", From 29139a16158e4ab7815dacabbd6ecbd57ce787b1 Mon Sep 17 00:00:00 2001 From: Ryan May Date: Mon, 5 Oct 2020 14:17:15 -0600 Subject: [PATCH 10/27] Backport PR #18639: nbagg: Don't close figures for bubbled events. --- lib/matplotlib/backends/web_backend/js/nbagg_mpl.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/matplotlib/backends/web_backend/js/nbagg_mpl.js b/lib/matplotlib/backends/web_backend/js/nbagg_mpl.js index 0f538979d19d..688e1953ebe1 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 @@ -181,6 +181,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, {}); }; From 64f1e53411beec78bb6cb5de37a34a47ac2a4c00 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Tue, 6 Oct 2020 19:44:19 -0400 Subject: [PATCH 11/27] Backport PR #18670: MNT: make certifi actually optional --- lib/matplotlib/__init__.py | 13 +++++++++++-- lib/matplotlib/image.py | 8 ++++++-- 2 files changed, 17 insertions(+), 4 deletions(-) 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(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(fname): if not isinstance(fname, Path) and is_url(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/image.py b/lib/matplotlib/image.py index ac5706ae7cf7..7d15e9f1c396 100644 --- a/lib/matplotlib/image.py +++ b/lib/matplotlib/image.py @@ -1481,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) From c5506744d3f1c68f38a8469b6e78558eb878a1cf Mon Sep 17 00:00:00 2001 From: Jody Klymak Date: Sat, 26 Sep 2020 17:22:24 -0700 Subject: [PATCH 12/27] Backport PR #18584: Fix setting 0-timeout timer with Tornado. --- lib/matplotlib/backends/backend_webagg_core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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): From 6818007f7620a1c22ed1c65ca83ba2c4208c565f Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Mon, 21 Sep 2020 17:38:03 -0400 Subject: [PATCH 13/27] Backport PR #18533: Correctly remove support for `\stackrel`. --- doc/api/prev_api_changes/api_changes_3.1.0.rst | 2 +- lib/matplotlib/mathtext.py | 8 -------- lib/matplotlib/tests/test_mathtext.py | 4 ---- tutorials/text/mathtext.py | 2 +- 4 files changed, 2 insertions(+), 14 deletions(-) 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/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/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/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:: From b627608807d1e2c52d1cfa0aa2b8ba9985d2a447 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Fri, 16 Oct 2020 19:02:47 -0400 Subject: [PATCH 14/27] Backport PR #18734: Fix deprecation warning in GitHub Actions. --- .github/workflows/reviewdog.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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: From ba13e4cf1b782d03c12668d5dbad72d79b0d8d96 Mon Sep 17 00:00:00 2001 From: Jody Klymak Date: Tue, 20 Oct 2020 11:28:52 -0700 Subject: [PATCH 15/27] Backport PR #18773: Update to latest cibuildwheel release. --- .github/workflows/cibuildwheel.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/cibuildwheel.yml b/.github/workflows/cibuildwheel.yml index 462be6d5a5b7..0ff85ef57bf4 100644 --- a/.github/workflows/cibuildwheel.yml +++ b/.github/workflows/cibuildwheel.yml @@ -28,7 +28,7 @@ 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: | @@ -39,7 +39,7 @@ jobs: 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 certifi numpy==1.15 From 1c509c3db7d845844fabf682004b50113669acf0 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Wed, 21 Oct 2020 15:35:21 -0400 Subject: [PATCH 16/27] Backport PR #18754: FIX: make sure we have more than 1 tick with small log ranges --- lib/matplotlib/tests/test_ticker.py | 10 ++++++++++ lib/matplotlib/ticker.py | 7 +++++++ 2 files changed, 17 insertions(+) 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/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) From bf9e17d095217d92d400b795487ecfc024d30708 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Tue, 20 Oct 2020 17:16:13 -0400 Subject: [PATCH 17/27] Backport PR #18769: Support `ax.grid(visible=)`. --- lib/matplotlib/axes/_base.py | 2 -- lib/matplotlib/axis.py | 43 +++++++++++----------- lib/matplotlib/tests/test_axes.py | 45 ++++++++++++++---------- lib/mpl_toolkits/axisartist/axislines.py | 18 +++++----- 4 files changed, 59 insertions(+), 49 deletions(-) diff --git a/lib/matplotlib/axes/_base.py b/lib/matplotlib/axes/_base.py index 62dee0983fdb..278dab735e6f 100644 --- a/lib/matplotlib/axes/_base.py +++ b/lib/matplotlib/axes/_base.py @@ -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/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index b31ebc945fac..57beec025aa8 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -4310,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(): 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) From be16bb4fb26723e20e4f9103b14aaf29c16de9cd Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Thu, 29 Oct 2020 17:28:55 -0400 Subject: [PATCH 18/27] Backport PR #18756: FIX: improve date performance regression --- lib/matplotlib/dates.py | 50 +++++++++++++++++------------------------ 1 file changed, 20 insertions(+), 30 deletions(-) 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): From c8324eea84c762842c29fe8193e87ac89129e96b Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Fri, 30 Oct 2020 03:18:28 +0100 Subject: [PATCH 19/27] Backport PR #18839: MNT: make sure we do not mutate input in Text.update --- lib/matplotlib/tests/test_text.py | 12 ++++++++++++ lib/matplotlib/text.py | 2 ++ 2 files changed, 14 insertions(+) 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/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) From 56fd5b8f18086ea234132a304d54ec45f591f05d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jouni=20K=2E=20Sepp=C3=A4nen?= Date: Sun, 2 Aug 2020 12:18:57 +0300 Subject: [PATCH 20/27] Backport PR #18134: Build on xcode9 --- .travis.yml | 22 +++++------------- ci/osx-deps | 65 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 16 deletions(-) create mode 100755 ci/osx-deps diff --git a/.travis.yml b/.travis.yml index d3b9780d8105..92579db8a2c2 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 @@ -169,7 +159,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/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 From 4ca9f0f914dc8232e50d2969e33731f1fa948d87 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Mon, 14 Sep 2020 11:55:22 -0400 Subject: [PATCH 21/27] CI: skip qt, cairo, gobject related installs on OSX on travis On 2020-09-12 pyqt5 replaced their wheels to have a minimum OSX version of 10.13 which caused us to fallback to trying to build pyqt5 from the tar.gz. This in turn failed (because we do not have any of the qt development libraries installed and even if we did it would take a while). We have always been installing pyside2 from wheels, but an older version (5.13.2) that has a fatal-to-us bug. However the previously published pyqt5 wheels were, despite being labeled as 10.12 actually complied against 10.13 and failed to import. This cause our test suite to decide that we did not have a valid qt binding and skip the qt tests. Now that pyqt5 is (correctly) not installing we are falling back to pyside2 and hitting the bug in pyside2 (it is reported to fixed in the next release 5.14.0 but that only has wheels for 10.13). PyGObject, pycairo, and cariocffi also do not install on OSX 10.12 This skips trying to install pycairo, pygobjoct, pyqt5, and pyside2 on OSX on travis because they all fail to install on OSX 10.12. It will make our CI marginally faster and does not move the status quo of what we were actually testing. --- .travis.yml | 35 ++++++++++++++++++++++------------- 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/.travis.yml b/.travis.yml index 92579db8a2c2..59e6f5b3fb5f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -133,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 && From 8988155bc5b97743da0e416a403c765b065398b8 Mon Sep 17 00:00:00 2001 From: Jody Klymak Date: Tue, 3 Nov 2020 14:04:36 -0800 Subject: [PATCH 22/27] Backport: manual --- lib/matplotlib/colorbar.py | 3 +++ 1 file changed, 3 insertions(+) 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: From 7ff0fbc6eac747d0580b64a13bc542a5e0435fd6 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Mon, 9 Nov 2020 22:17:04 -0500 Subject: [PATCH 23/27] Backport PR #18732: Add a ponyfill for ResizeObserver on older browsers. --- LICENSE/LICENSE_JSXTOOLS_RESIZE_OBSERVER | 108 ++++++++++++++++++ lib/matplotlib/backends/web_backend/js/mpl.js | 18 ++- .../backends/web_backend/js/nbagg_mpl.js | 1 + .../backends/web_backend/package.json | 3 + tools/embed_js.py | 102 +++++++++++++++++ 5 files changed, 230 insertions(+), 2 deletions(-) create mode 100644 LICENSE/LICENSE_JSXTOOLS_RESIZE_OBSERVER create mode 100644 tools/embed_js.py 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/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 688e1953ebe1..9c4ff87b5f7d 100644 --- a/lib/matplotlib/backends/web_backend/js/nbagg_mpl.js +++ b/lib/matplotlib/backends/web_backend/js/nbagg_mpl.js @@ -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(); 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/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) From 48f49dfbbe924ba4572d7cd54ed11be681abd82a Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Tue, 10 Nov 2020 15:15:26 -0500 Subject: [PATCH 24/27] Backport PR #18842: Add CPython 3.9 wheels. --- .github/workflows/cibuildwheel.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.github/workflows/cibuildwheel.yml b/.github/workflows/cibuildwheel.yml index 0ff85ef57bf4..b567c015dc2c 100644 --- a/.github/workflows/cibuildwheel.yml +++ b/.github/workflows/cibuildwheel.yml @@ -34,6 +34,16 @@ jobs: 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 From f679026f3979fa11f89adaae7d9808c75df4493d Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Wed, 11 Nov 2020 15:42:27 -0500 Subject: [PATCH 25/27] Backport PR #18929: FIX: make sure scalarmappable updates are handled correctly in 3D --- lib/mpl_toolkits/mplot3d/art3d.py | 105 +++++++++++++++++++++---- lib/mpl_toolkits/tests/test_mplot3d.py | 24 +++++- 2 files changed, 113 insertions(+), 16 deletions(-) diff --git a/lib/mpl_toolkits/mplot3d/art3d.py b/lib/mpl_toolkits/mplot3d/art3d.py index 02a310bd92bb..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 diff --git a/lib/mpl_toolkits/tests/test_mplot3d.py b/lib/mpl_toolkits/tests/test_mplot3d.py index 7d4d9d3e1c77..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']) @@ -1087,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') From 1158688fd5e03af8ed2e8028bae106095204125a Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Wed, 11 Nov 2020 21:20:17 -0500 Subject: [PATCH 26/27] DOC: Update GitHub stats for 3.3.3. --- doc/users/github_stats.rst | 141 +++++++++--------- .../prev_whats_new/github_stats_3.3.2.rst | 89 +++++++++++ 2 files changed, 161 insertions(+), 69 deletions(-) create mode 100644 doc/users/prev_whats_new/github_stats_3.3.2.rst 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 From 5a4f1b675da3d17df2d77d03bceab331afcc21db Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Wed, 11 Nov 2020 22:12:59 -0500 Subject: [PATCH 27/27] REL: v3.3.3 This is the third bugfix release of the 3.3.x series. This release contains several critical bug-fixes: * Fix calls to `Axis.grid` with argument `visible=True`. * Fix fully masked `imshow`. * Fix inconsistent color mapping in scatter for 3D plots. * Fix notebook/nbAgg figures when used with ipywidgets in the same cell. * Fix notebook/nbAgg/WebAgg on older (e.g., Firefox ESR) browsers. * Fix pcolormesh with `datetime` coordinates. * Fix performance regression with `datetime`s. * Fix singular ticks with small log ranges. * Fix timers/animations on wx and notebook backends. * Remove certifi as a hard runtime dependency.