From 404fe52d75887ae9d20e8e211a31cb1fb132143b Mon Sep 17 00:00:00 2001 From: Jody Klymak Date: Sat, 8 Feb 2020 16:08:31 -0800 Subject: [PATCH] FIX: add shading='nearest' and warn on mis-sized shading='flat' --- .../2020-01-18-pcolorshadingoptions.rst | 25 ++ .../pcolormesh_grids.py | 132 +++++++++++ .../pcolormesh_levels.py | 67 +++++- lib/matplotlib/axes/_axes.py | 221 ++++++++++++------ lib/matplotlib/colorbar.py | 3 +- lib/matplotlib/pyplot.py | 12 +- lib/matplotlib/rcsetup.py | 3 + lib/matplotlib/tests/test_axes.py | 79 +++++-- lib/matplotlib/tests/test_colorbar.py | 5 + .../tests/test_constrainedlayout.py | 5 +- lib/matplotlib/tests/test_transforms.py | 4 +- .../tests/test_axisartist_axislines.py | 5 +- matplotlibrc.template | 1 + 13 files changed, 459 insertions(+), 103 deletions(-) create mode 100644 doc/users/next_whats_new/2020-01-18-pcolorshadingoptions.rst create mode 100644 examples/images_contours_and_fields/pcolormesh_grids.py diff --git a/doc/users/next_whats_new/2020-01-18-pcolorshadingoptions.rst b/doc/users/next_whats_new/2020-01-18-pcolorshadingoptions.rst new file mode 100644 index 000000000000..a61d5f4f4c6a --- /dev/null +++ b/doc/users/next_whats_new/2020-01-18-pcolorshadingoptions.rst @@ -0,0 +1,25 @@ +Pcolor and Pcolormesh now accept shading='nearest' and 'auto' +------------------------------------------------------------- + +Previously `.axes.Axes.pcolor` and `.axes.Axes.pcolormesh` handled +the situation where *x* and *y* have the same (respective) size as *C* by +dropping the last row and column of *C*, and *x* and *y* are regarded as the +edges of the remaining rows and columns in *C*. However, many users want +*x* and *y* centered on the rows and columns of *C*. + +To accommodate this, ``shading='nearest'`` and ``shading='auto'`` are +new allowed strings for the ``shading`` kwarg. ``'nearest'`` will center the +color on *x* and *y* if *x* and *y* have the same dimensions as *C* +(otherwise an error will be thrown). ``shading='auto'`` will choose 'flat' +or 'nearest' based on the size of *X*, *Y*, *C*. + +If ``shading='flat'`` then *X*, and *Y* should have dimensions one larger +than *C*. If *X* and *Y* have the same dimensions as *C*, then the previous +behavior is used and the last row and column of *C* are dropped, and a +DeprecationWarning is emitted. + +Users can also specify this by the new :rc:`pcolor.shading` in their +``.matplotlibrc`` or via `.rcParams`. + +See :doc:`pcolormesh ` +for examples. diff --git a/examples/images_contours_and_fields/pcolormesh_grids.py b/examples/images_contours_and_fields/pcolormesh_grids.py new file mode 100644 index 000000000000..3cc8684759c1 --- /dev/null +++ b/examples/images_contours_and_fields/pcolormesh_grids.py @@ -0,0 +1,132 @@ +""" +============================ +pcolormesh grids and shading +============================ + +`.axes.Axes.pcolormesh` and `~.axes.Axes.pcolor` have a few options for +how grids are laid out and the shading between the grid points. + +Generally, if *Z* has shape *(M, N)* then the grid *X* and *Y* can be +specified with either shape *(M+1, N+1)* or *(M, N)*, depending on the +argument for the ``shading`` keyword argument. Note that below we specify +vectors *x* as either length N or N+1 and *y* as length M or M+1, and +`~.axes.Axes.pcolormesh` internally makes the mesh matrices *X* and *Y* from +the input vectors. + +""" + +import matplotlib +import matplotlib.pyplot as plt +import numpy as np + +############################################################################### +# Flat Shading +# ------------ +# +# The grid specification with the least assumptions is ``shading='flat'`` +# and if the grid is one larger than the data in each dimesion, i.e. has shape +# *(M+1, N+1)*. In that case *X* and *Y* sepcify the corners of quadrilaterals +# that are colored with the values in *Z*. Here we specify the edges of the +# *(3, 5)* quadrilaterals with *X* and *Y* that are *(4, 6)*. + +nrows = 3 +ncols = 5 +Z = np.arange(nrows * ncols).reshape(nrows, ncols) +x = np.arange(ncols + 1) +y = np.arange(nrows + 1) + +fig, ax = plt.subplots() +ax.pcolormesh(x, y, Z, shading='flat', vmin=Z.min(), vmax=Z.max()) + + +def _annotate(ax, x, y, title): + # this all gets repeated below: + X, Y = np.meshgrid(x, y) + ax.plot(X.flat, Y.flat, 'o', color='m') + ax.set_xlim(-0.7, 5.2) + ax.set_ylim(-0.7, 3.2) + ax.set_title(title) + +_annotate(ax, x, y, "shading='flat'") + + +############################################################################### +# Flat Shading, same shape grid +# ----------------------------- +# +# Often, however, data is provided where *X* and *Y* match the shape of *Z*. +# As of Matplotlib v3.3, ``shading='flat'`` is deprecated when this is the +# case, a warning is raised, and the last row and column of *Z* are dropped. +# This dropping of the last row and column is what Matplotlib did silently +# previous to v3.3, and is compatible with what Matlab does. + +x = np.arange(ncols) # note *not* ncols + 1 as before +y = np.arange(nrows) +fig, ax = plt.subplots() +ax.pcolormesh(x, y, Z, shading='flat', vmin=Z.min(), vmax=Z.max()) +_annotate(ax, x, y, "shading='flat': X, Y, C same shape") + +############################################################################### +# Nearest Shading, same shape grid +# -------------------------------- +# +# Usually, dropping a row and column of data is not what the user means when +# the make *X*, *Y* and *Z* all the same shape. For this case, Matplotlib +# allows ``shading='nearest'`` and centers the colored qudrilaterals on the +# grid points. +# +# If a grid that is not the correct shape is passed with ``shading='nearest'`` +# an error is raised. + +fig, ax = plt.subplots() +ax.pcolormesh(x, y, Z, shading='nearest', vmin=Z.min(), vmax=Z.max()) +_annotate(ax, x, y, "shading='nearest'") + +############################################################################### +# Auto Shading +# ------------ +# +# Its possible that the user would like the code to automatically choose +# which to use, in which case ``shading='auto'`` will decide whether to +# use 'flat' or 'nearest' shading based on the shapes of *X*, *Y* and *Z*. + +fig, axs = plt.subplots(2, 1, constrained_layout=True) +ax = axs[0] +x = np.arange(ncols) +y = np.arange(nrows) +ax.pcolormesh(x, y, Z, shading='auto', vmin=Z.min(), vmax=Z.max()) +_annotate(ax, x, y, "shading='auto'; X, Y, Z: same shape (nearest)") + +ax = axs[1] +x = np.arange(ncols + 1) +y = np.arange(nrows + 1) +ax.pcolormesh(x, y, Z, shading='auto', vmin=Z.min(), vmax=Z.max()) +_annotate(ax, x, y, "shading='auto'; X, Y one larger than Z (flat)") + +############################################################################### +# Gouraud Shading +# --------------- +# +# `Gouraud shading `_ can also +# be specified, where the colour in the quadrilaterals is linearly +# interpolated between the grid points. The shapes of *X*, *Y*, *Z* must +# be the same. + +fig, ax = plt.subplots(constrained_layout=True) +x = np.arange(ncols) +y = np.arange(nrows) +ax.pcolormesh(x, y, Z, shading='gouraud', vmin=Z.min(), vmax=Z.max()) +_annotate(ax, x, y, "shading='gouraud'; X, Y same shape as Z") + +plt.show() +############################################################################# +# +# ------------ +# +# References +# """""""""" +# +# The use of the following functions and methods is shown in this example: + +matplotlib.axes.Axes.pcolormesh +matplotlib.pyplot.pcolormesh diff --git a/examples/images_contours_and_fields/pcolormesh_levels.py b/examples/images_contours_and_fields/pcolormesh_levels.py index 8099164f2129..1bec248a9936 100644 --- a/examples/images_contours_and_fields/pcolormesh_levels.py +++ b/examples/images_contours_and_fields/pcolormesh_levels.py @@ -3,9 +3,9 @@ pcolormesh ========== -Shows how to combine Normalization and Colormap instances to draw "levels" in -`~.axes.Axes.pcolor`, `~.axes.Axes.pcolormesh` and `~.axes.Axes.imshow` type -plots in a similar way to the levels keyword argument to contour/contourf. +`.axes.Axes.pcolormesh` allows you to generate 2-D image-style plots. Note it +is faster than the similar `~.axes.Axes.pcolor`. + """ import matplotlib @@ -14,6 +14,67 @@ from matplotlib.ticker import MaxNLocator import numpy as np +############################################################################### +# Basic pcolormesh +# ---------------- +# +# We usually specify a pcolormesh by defining the edge of quadrilaterals and +# the value of the quadrilateral. Note that here *x* and *y* each have one +# extra element than Z in the respective dimension. + +np.random.seed(19680801) +Z = np.random.rand(6, 10) +x = np.arange(-0.5, 10, 1) # len = 11 +y = np.arange(4.5, 11, 1) # len = 7 + +fig, ax = plt.subplots() +ax.pcolormesh(x, y, Z) + +############################################################################### +# Non-rectilinear pcolormesh +# -------------------------- +# +# Note that we can also specify matrices for *X* and *Y* and have +# non-rectilinear quadrilaterals. + +x = np.arange(-0.5, 10, 1) # len = 11 +y = np.arange(4.5, 11, 1) # len = 7 +X, Y = np.meshgrid(x, y) +X = X + 0.2 * Y # tilt the coordinates. +Y = Y + 0.3 * X + +fig, ax = plt.subplots() +ax.pcolormesh(X, Y, Z) + +############################################################################### +# Centered Coordinates +# --------------------- +# +# Often a user wants to pass *X* and *Y* with the same sizes as *Z* to +# `.axes.Axes.pcolormesh`. This is also allowed if ``shading='auto'`` is +# passed (default set by :rc:`pcolor.shading`). Pre Matplotlib 3.3, +# ``shading='flat'`` would drop the last column and row of *Z*; while that +# is still allowed for back compatibility purposes, a DeprecationWarning is +# raised. + +x = np.arange(10) # len = 10 +y = np.arange(6) # len = 6 +X, Y = np.meshgrid(x, y) + +fig, axs = plt.subplots(2, 1, sharex=True, sharey=True) +axs[0].pcolormesh(X, Y, Z, vmin=np.min(Z), vmax=np.max(Z), shading='auto') +axs[0].set_title("shading='auto' = 'nearest'") +axs[1].pcolormesh(X, Y, Z, vmin=np.min(Z), vmax=np.max(Z), shading='flat') +axs[1].set_title("shading='flat'") + +############################################################################### +# Making levels using Norms +# ------------------------- +# +# Shows how to combine Normalization and Colormap instances to draw +# "levels" in `.axes.Axes.pcolor`, `.axes.Axes.pcolormesh` +# and `.axes.Axes.imshow` type plots in a similar +# way to the levels keyword argument to contour/contourf. # make these smaller to increase the resolution dx, dy = 0.05, 0.05 diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index d257b2c1f0e3..5e8379fe01fe 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -5624,24 +5624,23 @@ def imshow(self, X, cmap=None, norm=None, aspect=None, return im @staticmethod - def _pcolorargs(funcname, *args, allmatch=False): - # If allmatch is True, then the incoming X, Y, C must have matching - # dimensions, taking into account that X and Y can be 1-D rather than - # 2-D. This perfect match is required for Gouraud shading. For flat - # shading, X and Y specify boundaries, so we need one more boundary - # than color in each direction. For convenience, and consistent with - # Matlab, we discard the last row and/or column of C if necessary to - # meet this condition. This is done if allmatch is False. + def _pcolorargs(funcname, *args, shading='flat'): + # - 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; + # - reset shading if shading='auto' to flat or nearest + # depending on size; if len(args) == 1: C = np.asanyarray(args[0]) nrows, ncols = C.shape - if allmatch: + if shading in ['gouraud', 'nearest']: X, Y = np.meshgrid(np.arange(ncols), np.arange(nrows)) else: X, Y = np.meshgrid(np.arange(ncols + 1), np.arange(nrows + 1)) + shading = 'flat' C = cbook.safe_masked_invalid(C) - return X, Y, C + return X, Y, C, shading if len(args) == 3: # Check x and y for bad data... @@ -5676,24 +5675,63 @@ def _pcolorargs(funcname, *args, allmatch=False): raise TypeError( 'Incompatible X, Y inputs to %s; see help(%s)' % ( funcname, funcname)) - if allmatch: - if (Nx, Ny) != (ncols, nrows): + + if shading == 'auto': + if ncols == Nx and nrows == Ny: + shading = 'nearest' + else: + shading = 'flat' + + if shading == 'flat': + if not (ncols in (Nx, Nx - 1) and nrows in (Ny, Ny - 1)): raise TypeError('Dimensions of C %s are incompatible with' ' X (%d) and/or Y (%d); see help(%s)' % ( C.shape, Nx, Ny, funcname)) - else: - if not (ncols in (Nx, Nx - 1) and nrows in (Ny, Ny - 1)): + if (ncols == Nx or nrows == Ny): + cbook.warn_deprecated("3.3", + message="shading='flat' when X and Y have the same " + "dimensions as C is deprecated since %(since)s. " + "Either specify the corners of the quadrilaterals " + "with X and Y, or pass shading='auto', 'nearest' " + "or 'gouraud', or set rcParams['pcolor.shading']") + C = C[:Ny - 1, :Nx - 1] + else: # ['nearest', 'gouraud']: + if (Nx, Ny) != (ncols, nrows): raise TypeError('Dimensions of C %s are incompatible with' ' X (%d) and/or Y (%d); see help(%s)' % ( C.shape, Nx, Ny, funcname)) - C = C[:Ny - 1, :Nx - 1] + if shading in ['nearest', 'auto']: + # grid is specified at the center, so define corners + # at the midpoints between the grid centers and then use the + # flat algorithm. + def _interp_grid(X): + # helper for below + if np.shape(X)[1] > 1: + dX = np.diff(X, axis=1)/2. + X = np.hstack((X[:, [0]] - dX[:, [0]], + X[:, :-1] + dX, + X[:, [-1]] + dX[:, [-1]])) + else: + # This is just degenerate, but we can't reliably guess + # a dX if there is just one value. + X = np.hstack((X, X)) + return X + + if ncols == Nx: + X = _interp_grid(X) + Y = _interp_grid(Y) + if nrows == Ny: + X = _interp_grid(X.T).T + Y = _interp_grid(Y.T).T + shading = 'flat' + C = cbook.safe_masked_invalid(C) - return X, Y, C + return X, Y, C, shading @_preprocess_data() @docstring.dedent_interpd - def pcolor(self, *args, alpha=None, norm=None, cmap=None, vmin=None, - vmax=None, **kwargs): + def pcolor(self, *args, shading=None, alpha=None, norm=None, cmap=None, + vmin=None, vmax=None, **kwargs): r""" Create a pseudocolor plot with a non-regular rectangular grid. @@ -5707,7 +5745,9 @@ def pcolor(self, *args, alpha=None, norm=None, cmap=None, vmin=None, ``pcolor()`` can be very slow for large arrays. In most cases you should use the similar but much faster - `~.Axes.pcolormesh` instead. See there for a discussion of the + `~.Axes.pcolormesh` instead. See + :ref:`Differences between pcolor() and pcolormesh() + ` for a discussion of the differences. Parameters @@ -5716,28 +5756,51 @@ def pcolor(self, *args, alpha=None, norm=None, cmap=None, vmin=None, A scalar 2-D array. The values will be color-mapped. X, Y : array-like, optional - The coordinates of the quadrilateral corners. The quadrilateral - for ``C[i, j]`` has corners at:: + The coordinates of the corners of quadrilaterals of a pcolormesh:: + + (X[i+1, j], Y[i+1, j]) (X[i+1, j+1], Y[i+1, j+1]) + +-----+ + | | + +-----+ + (X[i, j], Y[i, j]) (X[i, j+1], Y[i, j+1]) - (X[i+1, j], Y[i+1, j]) (X[i+1, j+1], Y[i+1, j+1]) - +---------+ - | C[i, j] | - +---------+ - (X[i, j], Y[i, j]) (X[i, j+1], Y[i, j+1]) + Note that the column index corresponds to the x-coordinate, and + the row index corresponds to y. For details, see the + :ref:`Notes ` section below. - Note that the column index corresponds to the - x-coordinate, and the row index corresponds to y. For - details, see the :ref:`Notes ` - section below. + If ``shading='flat'`` the dimensions of *X* and *Y* should be one + greater than those of *C*, and the quadrilateral is colored due + to the value at ``C[i, j]``. If *X*, *Y* and *C* have equal + dimensions, a warning will be raised and the last row and column + of *C* will be ignored. - The dimensions of *X* and *Y* should be one greater than those of - *C*. Alternatively, *X*, *Y* and *C* may have equal dimensions, in - which case the last row and column of *C* will be ignored. + If ``shading='nearest'``, the dimensions of *X* and *Y* should be + the same as those of *C* (if not, a ValueError will be raised). The + color ``C[i, j]`` will be centered on ``(X[i, j], Y[i, j])``. If *X* and/or *Y* are 1-D arrays or column vectors they will be expanded as needed into the appropriate 2-D arrays, making a rectangular grid. + shading : {'flat', 'nearest', 'auto'}, optional + The fill style for the quadrilateral; defaults to 'flat' or + ':rc:`pcolor.shading`. Possible values: + + - 'flat': A solid color is used for each quad. The color of the + quad (i, j), (i+1, j), (i, j+1), (i+1, j+1) is given by + ``C[i, j]``. The dimensions of *X* and *Y* should be + one greater than those of *C*; if they are the same as *C*, + then a deprecation warning is raised, and the last row + and column of *C* are dropped. + - 'nearest': Each grid point will have a color centered on it, + extending halfway between the adjacent grid centers. The + dimensions of *X* and *Y* must be the same as *C*. + - 'auto': Choose 'flat' if dimensions of *X* and *Y* are one + larger than *C*. Choose 'nearest' if dimensions are the same. + + See :doc:`/gallery/images_contours_and_fields/pcolormesh_grids` + for more description. + cmap : str or `~matplotlib.colors.Colormap`, default: :rc:`image.cmap` A Colormap instance or registered colormap name. The colormap maps the *C* values to colors. @@ -5817,18 +5880,12 @@ def pcolor(self, *args, alpha=None, norm=None, cmap=None, vmin=None, The grid orientation follows the standard matrix convention: An array *C* with shape (nrows, ncolumns) is plotted with the column number as *X* and the row number as *Y*. - - **Handling of pcolor() end-cases** - - ``pcolor()`` displays all columns of *C* if *X* and *Y* are not - specified, or if *X* and *Y* have one more column than *C*. - If *X* and *Y* have the same number of columns as *C* then the last - column of *C* is dropped. Similarly for the rows. - - Note: This behavior is different from MATLAB's ``pcolor()``, which - always discards the last row and column of *C*. """ - X, Y, C = self._pcolorargs('pcolor', *args, allmatch=False) + + if shading is None: + shading = rcParams['pcolor.shading'] + shading = shading.lower() + X, Y, C, shading = self._pcolorargs('pcolor', *args, shading=shading) Ny, Nx = X.shape # unit conversion allows e.g. datetime objects as axis values @@ -5924,19 +5981,19 @@ def pcolor(self, *args, alpha=None, norm=None, cmap=None, vmin=None, @_preprocess_data() @docstring.dedent_interpd def pcolormesh(self, *args, alpha=None, norm=None, cmap=None, vmin=None, - vmax=None, shading='flat', antialiased=False, **kwargs): + vmax=None, shading=None, antialiased=False, **kwargs): """ Create a pseudocolor plot with a non-regular rectangular grid. Call signature:: - pcolor([X, Y,] C, **kwargs) + pcolormesh([X, Y,] C, **kwargs) *X* and *Y* can be used to specify the corners of the quadrilaterals. - .. note:: + .. hint:: - `~.Axes.pcolormesh` is similar to `~.Axes.pcolor`. It's much faster + `~.Axes.pcolormesh` is similar to `~.Axes.pcolor`. It is much faster and preferred in most cases. For a detailed discussion on the differences see :ref:`Differences between pcolor() and pcolormesh() `. @@ -5947,23 +6004,29 @@ def pcolormesh(self, *args, alpha=None, norm=None, cmap=None, vmin=None, A scalar 2-D array. The values will be color-mapped. X, Y : array-like, optional - The coordinates of the quadrilateral corners. The quadrilateral - for ``C[i, j]`` has corners at:: - - (X[i+1, j], Y[i+1, j]) (X[i+1, j+1], Y[i+1, j+1]) - +---------+ - | C[i, j] | - +---------+ - (X[i, j], Y[i, j]) (X[i, j+1], Y[i, j+1]) - - Note that the column index corresponds to the - x-coordinate, and the row index corresponds to y. For - details, see the :ref:`Notes ` - section below. - - The dimensions of *X* and *Y* should be one greater than those of - *C*. Alternatively, *X*, *Y* and *C* may have equal dimensions, in - which case the last row and column of *C* will be ignored. + The coordinates of the corners of quadrilaterals of a pcolormesh:: + + (X[i+1, j], Y[i+1, j]) (X[i+1, j+1], Y[i+1, j+1]) + +-----+ + | | + +-----+ + (X[i, j], Y[i, j]) (X[i, j+1], Y[i, j+1]) + + Note that the column index corresponds to the x-coordinate, and + the row index corresponds to y. For details, see the + :ref:`Notes ` section below. + + If ``shading='flat'`` the dimensions of *X* and *Y* should be one + greater than those of *C*, and the quadrilateral is colored due + to the value at ``C[i, j]``. If *X*, *Y* and *C* have equal + dimensions, a warning will be raised and the last row and column + of *C* will be ignored. + + If ``shading='nearest'`` or ``'gouraud'``, the dimensions of *X* + and *Y* should be the same as those of *C* (if not, a ValueError + will be raised). For ``'nearest'`` the color ``C[i, j]`` is + centered on ``(X[i, j], Y[i, j])``. For ``'gouraud'``, a smooth + interpolation is caried out between the quadrilateral corners. If *X* and/or *Y* are 1-D arrays or column vectors they will be expanded as needed into the appropriate 2-D arrays, making a @@ -5999,16 +6062,29 @@ def pcolormesh(self, *args, alpha=None, norm=None, cmap=None, vmin=None, alpha : scalar, default: None The alpha blending value, between 0 (transparent) and 1 (opaque). - shading : {'flat', 'gouraud'}, optional - The fill style, Possible values: + shading : {'flat', 'nearest', 'gouraud', 'auto'}, optional + The fill style for the quadrilateral; defaults to + 'flat' or ':rc:`pcolor.shading`. Possible values: - 'flat': A solid color is used for each quad. The color of the quad (i, j), (i+1, j), (i, j+1), (i+1, j+1) is given by - ``C[i, j]``. + ``C[i, j]``. The dimensions of *X* and *Y* should be + one greater than those of *C*; if they are the same as *C*, + then a deprecation warning is raised, and the last row + and column of *C* are dropped. + - 'nearest': Each grid point will have a color centered on it, + extending halfway between the adjacent grid centers. The + dimensions of *X* and *Y* must be the same as *C*. - 'gouraud': Each quad will be Gouraud shaded: The color of the corners (i', j') are given by ``C[i', j']``. The color values of the area in between is interpolated from the corner values. - When Gouraud shading is used, *edgecolors* is ignored. + The dimensions of *X* and *Y* must be the same as *C*. When + Gouraud shading is used, *edgecolors* is ignored. + - 'auto': Choose 'flat' if diemnsions of *X* and *Y* are one + larger than *C*. Choose 'nearest' if dimensions are the same. + + See :doc:`/gallery/images_contours_and_fields/pcolormesh_grids` + for more description. snap : bool, default: False Whether to snap the mesh to pixel boundaries. @@ -6079,12 +6155,13 @@ def pcolormesh(self, *args, alpha=None, norm=None, cmap=None, vmin=None, `~.Axes.pcolormesh`, which is not available with `~.Axes.pcolor`. """ + if shading is None: + shading = rcParams['pcolor.shading'] shading = shading.lower() kwargs.setdefault('edgecolors', 'None') - allmatch = (shading == 'gouraud') - - X, Y, C = self._pcolorargs('pcolormesh', *args, allmatch=allmatch) + X, Y, C, shading = self._pcolorargs('pcolormesh', *args, + shading=shading) Ny, Nx = X.shape X = X.ravel() Y = Y.ravel() diff --git a/lib/matplotlib/colorbar.py b/lib/matplotlib/colorbar.py index 72f30723ae64..ff583425a4b8 100644 --- a/lib/matplotlib/colorbar.py +++ b/lib/matplotlib/colorbar.py @@ -421,7 +421,6 @@ def __init__(self, ax, cmap=None, ticklocation=ticklocation) cbook._check_in_list( ['uniform', 'proportional'], spacing=spacing) - self.ax = ax self._patch_ax() if cmap is None: @@ -803,7 +802,7 @@ def _add_solids(self, X, Y, C): alpha=self.alpha, edgecolors='None') _log.debug('Setting pcolormesh') - col = self.ax.pcolormesh(*args, **kw) + col = self.ax.pcolormesh(*args, **kw, shading='flat') # self.add_observer(col) # We should observe, not be observed... if self.solids is not None: diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index 01b196045886..c8ae0a0dda3e 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -2527,12 +2527,12 @@ def minorticks_on(): # Autogenerated by boilerplate.py. Do not edit as changes will be lost. @docstring.copy(Axes.pcolor) def pcolor( - *args, alpha=None, norm=None, cmap=None, vmin=None, - vmax=None, data=None, **kwargs): + *args, shading=None, alpha=None, norm=None, cmap=None, + vmin=None, vmax=None, data=None, **kwargs): __ret = gca().pcolor( - *args, alpha=alpha, norm=norm, cmap=cmap, vmin=vmin, - vmax=vmax, **({"data": data} if data is not None else {}), - **kwargs) + *args, shading=shading, alpha=alpha, norm=norm, cmap=cmap, + vmin=vmin, vmax=vmax, **({"data": data} if data is not None + else {}), **kwargs) sci(__ret) return __ret @@ -2541,7 +2541,7 @@ def pcolor( @docstring.copy(Axes.pcolormesh) def pcolormesh( *args, alpha=None, norm=None, cmap=None, vmin=None, - vmax=None, shading='flat', antialiased=False, data=None, + vmax=None, shading=None, antialiased=False, data=None, **kwargs): __ret = gca().pcolormesh( *args, alpha=alpha, norm=norm, cmap=cmap, vmin=vmin, diff --git a/lib/matplotlib/rcsetup.py b/lib/matplotlib/rcsetup.py index 7e94c07363ea..c1beff67fd0d 100644 --- a/lib/matplotlib/rcsetup.py +++ b/lib/matplotlib/rcsetup.py @@ -1037,6 +1037,9 @@ def validate_webagg_address(s): # marker props 'markers.fillstyle': ['full', validate_fillstyle], + ## pcolor(mesh) props: + 'pcolor.shading': ['flat', validate_string], # auto,flat,nearest,gouraud + ## patch props 'patch.linewidth': [1.0, validate_float], # line width in points 'patch.edgecolor': ['black', validate_color], diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index 765277949ac8..970b3feae44a 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -1152,8 +1152,8 @@ def test_pcolorargs_5205(): plt.pcolor(Z) plt.pcolor(list(Z)) - plt.pcolor(x, y, Z) - plt.pcolor(X, Y, list(Z)) + plt.pcolor(x, y, Z[:-1, :-1]) + plt.pcolor(X, Y, list(Z[:-1, :-1])) @image_comparison(['pcolormesh'], remove_text=True) @@ -1172,8 +1172,8 @@ def test_pcolormesh(): Zm = ma.masked_where(np.abs(Qz) < 0.5 * np.max(Qz), Z) fig, (ax1, ax2, ax3) = plt.subplots(1, 3) - ax1.pcolormesh(Qx, Qz, Z, lw=0.5, edgecolors='k') - ax2.pcolormesh(Qx, Qz, Z, lw=2, edgecolors=['b', 'w']) + ax1.pcolormesh(Qx, Qz, Z[:-1, :-1], lw=0.5, edgecolors='k') + ax2.pcolormesh(Qx, Qz, Z[:-1, :-1], lw=2, edgecolors=['b', 'w']) ax3.pcolormesh(Qx, Qz, Z, shading="gouraud") @@ -1201,10 +1201,11 @@ def test_pcolormesh_alpha(): (0, -1.5), 1.5, 3, facecolor=[.7, .1, .1, .5], zorder=0 )) # ax1, ax2: constant alpha - ax1.pcolormesh(Qx, Qy, Z, cmap=vir, alpha=0.4, shading='flat', zorder=1) + ax1.pcolormesh(Qx, Qy, Z[:-1, :-1], cmap=vir, alpha=0.4, + shading='flat', zorder=1) ax2.pcolormesh(Qx, Qy, Z, cmap=vir, alpha=0.4, shading='gouraud', zorder=1) # ax3, ax4: alpha from colormap - ax3.pcolormesh(Qx, Qy, Z, cmap=cmap, shading='flat', zorder=1) + ax3.pcolormesh(Qx, Qy, Z[:-1, :-1], cmap=cmap, shading='flat', zorder=1) ax4.pcolormesh(Qx, Qy, Z, cmap=cmap, shading='gouraud', zorder=1) @@ -1219,13 +1220,13 @@ def test_pcolormesh_datetime_axis(): z1, z2 = np.meshgrid(np.arange(20), np.arange(20)) z = z1 * z2 plt.subplot(221) - plt.pcolormesh(x[:-1], y[:-1], z) + plt.pcolormesh(x[:-1], y[:-1], z[:-1, :-1]) plt.subplot(222) plt.pcolormesh(x, y, z) x = np.repeat(x[np.newaxis], 21, axis=0) y = np.repeat(y[:, np.newaxis], 21, axis=1) plt.subplot(223) - plt.pcolormesh(x[:-1, :-1], y[:-1, :-1], z) + plt.pcolormesh(x[:-1, :-1], y[:-1, :-1], z[:-1, :-1]) plt.subplot(224) plt.pcolormesh(x, y, z) for ax in fig.get_axes(): @@ -1245,13 +1246,13 @@ def test_pcolor_datetime_axis(): z1, z2 = np.meshgrid(np.arange(20), np.arange(20)) z = z1 * z2 plt.subplot(221) - plt.pcolor(x[:-1], y[:-1], z) + plt.pcolor(x[:-1], y[:-1], z[:-1, :-1]) plt.subplot(222) plt.pcolor(x, y, z) x = np.repeat(x[np.newaxis], 21, axis=0) y = np.repeat(y[:, np.newaxis], 21, axis=1) plt.subplot(223) - plt.pcolor(x[:-1, :-1], y[:-1, :-1], z) + plt.pcolor(x[:-1, :-1], y[:-1, :-1], z[:-1, :-1]) plt.subplot(224) plt.pcolor(x, y, z) for ax in fig.get_axes(): @@ -1285,6 +1286,56 @@ def test_pcolorargs(): ax.pcolormesh(x, y, Z[:-1, :-1]) +@check_figures_equal(extensions=["png"]) +def test_pcolornearest(fig_test, fig_ref): + ax = fig_test.subplots() + x = np.arange(0, 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 = x[:-1] + np.diff(x) / 2 + 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() + x = np.arange(0, 10) + y = np.arange(0, 4) + np.random.seed(19680801) + Z = np.random.randn(3, 9) + # fake dropping the data + ax.pcolormesh(x[:-1], y[:-1], Z[:-1, :-1], shading='flat') + + ax = fig_ref.subplots() + # test dropping the data... + x2 = x[:-1] + y2 = y[:-1] + with pytest.warns(MatplotlibDeprecationWarning): + ax.pcolormesh(x2, y2, Z, shading='flat') + + +@check_figures_equal(extensions=["png"]) +def test_pcolorauto(fig_test, fig_ref): + ax = fig_test.subplots() + x = np.arange(0, 10) + y = np.arange(0, 4) + np.random.seed(19680801) + Z = np.random.randn(3, 9) + ax.pcolormesh(x, y, Z, shading='auto') + + ax = fig_ref.subplots() + # specify the centers + x2 = x[:-1] + np.diff(x) / 2 + y2 = y[:-1] + np.diff(y) / 2 + ax.pcolormesh(x2, y2, Z, shading='auto') + + @image_comparison(['canonical']) def test_canonical(): fig, ax = plt.subplots() @@ -5793,7 +5844,7 @@ def test_broken_barh_timedelta(): def test_pandas_pcolormesh(pd): time = pd.date_range('2000-01-01', periods=10) depth = np.arange(20) - data = np.random.rand(20, 10) + data = np.random.rand(19, 9) fig, ax = plt.subplots() ax.pcolormesh(time, depth, data) @@ -6279,7 +6330,7 @@ def test_inset(): z = np.sin(x) ** 10 + np.cos(10 + y * x) * np.cos(x) fig, ax = plt.subplots() - ax.pcolormesh(x, y, z) + ax.pcolormesh(x, y, z[:-1, :-1]) ax.set_aspect(1.) ax.apply_aspect() # we need to apply_aspect to make the drawing below work. @@ -6305,7 +6356,7 @@ def test_zoom_inset(): z = np.sin(x)**10 + np.cos(10 + y*x) * np.cos(x) fig, ax = plt.subplots() - ax.pcolormesh(x, y, z) + ax.pcolormesh(x, y, z[:-1, :-1]) ax.set_aspect(1.) ax.apply_aspect() # we need to apply_aspect to make the drawing below work. @@ -6313,7 +6364,7 @@ def test_zoom_inset(): # Make the inset_axes... Position axes coordinates... axin1 = ax.inset_axes([0.7, 0.7, 0.35, 0.35]) # redraw the data in the inset axes... - axin1.pcolormesh(x, y, z) + axin1.pcolormesh(x, y, z[:-1, :-1]) axin1.set_xlim([1.5, 2.15]) axin1.set_ylim([2, 2.5]) axin1.set_aspect(ax.get_aspect()) diff --git a/lib/matplotlib/tests/test_colorbar.py b/lib/matplotlib/tests/test_colorbar.py index 7f9b00b5e7c3..93dde7f96ca0 100644 --- a/lib/matplotlib/tests/test_colorbar.py +++ b/lib/matplotlib/tests/test_colorbar.py @@ -49,6 +49,8 @@ def _colorbar_extension_shape(spacing): # Get the appropriate norm and use it to get colorbar boundaries. norm = norms[extension_type] boundaries = values = norm.boundaries + # note that the last value was silently dropped pre 3.3: + values = values[:-1] # Create a subplot. cax = fig.add_subplot(4, 1, i + 1) # Generate the colorbar. @@ -79,6 +81,7 @@ def _colorbar_extension_length(spacing): # Get the appropriate norm and use it to get colorbar boundaries. norm = norms[extension_type] boundaries = values = norm.boundaries + values = values[:-1] for j, extendfrac in enumerate((None, 'auto', 0.1)): # Create a subplot. cax = fig.add_subplot(12, 1, i*3 + j + 1) @@ -333,6 +336,7 @@ def test_colorbar_autoticks(): y = np.arange(-4.0, 3.001) X, Y = np.meshgrid(x, y) Z = X * Y + Z = Z[:-1, :-1] pcm = ax[0].pcolormesh(X, Y, Z) cbar = fig.colorbar(pcm, ax=ax[0], extend='both', orientation='vertical') @@ -354,6 +358,7 @@ def test_colorbar_autotickslog(): y = np.arange(-4.0, 3.001) X, Y = np.meshgrid(x, y) Z = X * Y + Z = Z[:-1, :-1] pcm = ax[0].pcolormesh(X, Y, 10**Z, norm=LogNorm()) cbar = fig.colorbar(pcm, ax=ax[0], extend='both', orientation='vertical') diff --git a/lib/matplotlib/tests/test_constrainedlayout.py b/lib/matplotlib/tests/test_constrainedlayout.py index b5d088afa886..1f3c0e9ab148 100644 --- a/lib/matplotlib/tests/test_constrainedlayout.py +++ b/lib/matplotlib/tests/test_constrainedlayout.py @@ -24,9 +24,8 @@ def example_pcolor(ax, fontsize=12): y, x = np.mgrid[slice(-3, 3 + dy, dy), slice(-3, 3 + dx, dx)] z = (1 - x / 2. + x ** 5 + y ** 3) * np.exp(-x ** 2 - y ** 2) - pcm = ax.pcolormesh(x, y, z, cmap='RdBu_r', vmin=-1., vmax=1., + pcm = ax.pcolormesh(x, y, z[:-1, :-1], cmap='RdBu_r', vmin=-1., vmax=1., rasterized=True) - # ax.locator_params(nbins=3) ax.set_xlabel('x-label', fontsize=fontsize) ax.set_ylabel('y-label', fontsize=fontsize) ax.set_title('Title', fontsize=fontsize) @@ -319,7 +318,7 @@ def test_constrained_layout20(): fig = plt.figure() ax = fig.add_axes([0, 0, 1, 1]) - mesh = ax.pcolormesh(gx, gx, img) + mesh = ax.pcolormesh(gx, gx, img[:-1, :-1]) fig.colorbar(mesh) diff --git a/lib/matplotlib/tests/test_transforms.py b/lib/matplotlib/tests/test_transforms.py index 46d1218455c1..909231506b2a 100644 --- a/lib/matplotlib/tests/test_transforms.py +++ b/lib/matplotlib/tests/test_transforms.py @@ -119,7 +119,7 @@ def test_pcolor_pre_transform_limits(): # Based on test_contour_pre_transform_limits() ax = plt.axes() xs, ys = np.meshgrid(np.linspace(15, 20, 15), np.linspace(12.4, 12.5, 20)) - ax.pcolor(xs, ys, np.log(xs * ys), + ax.pcolor(xs, ys, np.log(xs * ys)[:-1, :-1], transform=mtransforms.Affine2D().scale(0.1) + ax.transData) expected = np.array([[1.5, 1.24], @@ -131,7 +131,7 @@ def test_pcolormesh_pre_transform_limits(): # Based on test_contour_pre_transform_limits() ax = plt.axes() xs, ys = np.meshgrid(np.linspace(15, 20, 15), np.linspace(12.4, 12.5, 20)) - ax.pcolormesh(xs, ys, np.log(xs * ys), + ax.pcolormesh(xs, ys, np.log(xs * ys)[:-1, :-1], transform=mtransforms.Affine2D().scale(0.1) + ax.transData) expected = np.array([[1.5, 1.24], diff --git a/lib/mpl_toolkits/tests/test_axisartist_axislines.py b/lib/mpl_toolkits/tests/test_axisartist_axislines.py index 3270e233d16f..7c2f83ff26ae 100644 --- a/lib/mpl_toolkits/tests/test_axisartist_axislines.py +++ b/lib/mpl_toolkits/tests/test_axisartist_axislines.py @@ -83,7 +83,10 @@ def test_ParasiteAxesAuxTrans(): ax2 = ParasiteAxesAuxTrans(ax1, IdentityTransform()) ax1.parasites.append(ax2) - getattr(ax2, name)(xx, yy, data) + if name.startswith('pcolor'): + getattr(ax2, name)(xx, yy, data[:-1, :-1]) + else: + getattr(ax2, name)(xx, yy, data) ax1.set_xlim((0, 5)) ax1.set_ylim((0, 5)) diff --git a/matplotlibrc.template b/matplotlibrc.template index dba5d6160cdb..afe7313c7e96 100644 --- a/matplotlibrc.template +++ b/matplotlibrc.template @@ -135,6 +135,7 @@ #markers.fillstyle : full ## {full, left, right, bottom, top, none} +# pcolor.shading : flat ## *************************************************************************** ## * PATCHES *