diff --git a/doc/users/whats_new.rst b/doc/users/whats_new.rst index 362384db8c94..7f05f59cdbc9 100644 --- a/doc/users/whats_new.rst +++ b/doc/users/whats_new.rst @@ -246,6 +246,14 @@ volumetric model. Improvements ++++++++++++ +New ``interpolate_grids`` keyword arg to `pcolormesh` +----------------------------------------------------- + +`pcolormesh` now has a keyword argument ``interpolate_grids`` that will allow +interpolation of the boundary grids ``X``, ``Y`` to match the dimension of +``C`` if Gouraud shading is used. This will simplify changing between flat +and Gouraud shading. + CheckButtons widget ``get_status`` function ------------------------------------------- diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index c730eba4fd6d..ff4c3a068ffc 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -5156,19 +5156,29 @@ def imshow(self, X, cmap=None, norm=None, aspect=None, @staticmethod def _pcolorargs(funcname, *args, **kw): - # This takes one kwarg, allmatch. - # 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 Gouroud 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. + # This function takes care of resizing the grids X and Y + # to match the size of the color grid C. + # + # If only a color grid is given uniform grids with step size 1.0 + # are created for X and Y are, matching the dimensions of C. + # + # If given, the X and Y grids must have the same size or + # one more row and column than C. + # + # If the keyword allmatch is given and True the returned X, Y and + # C grids will have the same dimensions. If this is not the case + # on input, this is achieved by linearly interpolating the grids + # to obtain their midpoints. This is what is required for gouraud + # shading. + # + # If the keyword allmatch is not given or False the returned + # X, Y will have one more row and column than the C grid, which + # is achieved by potentially dropping the last row and column of + # X and Y. This is consistent with Matlab behaviour. This is + # required for flat shading. allmatch = kw.pop("allmatch", False) + interpolate_grids = kw.pop("interpolate_grids", False) if len(args) == 1: C = np.asanyarray(args[0]) @@ -5200,16 +5210,35 @@ def _pcolorargs(funcname, *args, **kw): raise TypeError( 'Incompatible X, Y inputs to %s; see help(%s)' % ( funcname, funcname)) + + if not (numCols in (Nx, Nx - 1) and numRows 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)) + if allmatch: - if not (Nx == numCols and Ny == numRows): - raise TypeError('Dimensions of C %s are incompatible with' - ' X (%d) and/or Y (%d); see help(%s)' % ( - C.shape, Nx, Ny, funcname)) + if numRows != Ny or numCols != Nx: + if interpolate_grids: + + if numRows == Ny - 1: + Y_new = 0.5 * Y[:numRows, :numCols] + Y_new += 0.5 * Y[1:, 1:] + Y = Y_new + + if numCols == Nx - 1: + X_new = 0.5 * X[:numRows, :numCols] + X_new += 0.5 * X[1:, 1:] + X = X_new + + else: + + raise TypeError('Dimensions of C %s are incompatible with' + ' X (%d) and/or Y (%d); To allow linear ' + ' interpolation of the grids to match C ' + ' set the keyword interpolate_grids to ' + ' True' % (C.shape, Nx, Ny, funcname)) + else: - if not (numCols in (Nx, Nx - 1) and numRows 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)) C = C[:Ny - 1, :Nx - 1] C = cbook.safe_masked_invalid(C) return X, Y, C @@ -5498,6 +5527,22 @@ def pcolormesh(self, *args, **kwargs): contrast, :func:`~matplotlib.pyplot.pcolor` simply does not draw quadrilaterals with masked colors or vertices. + If flat shading is used, the arrays *X* and *Y* are assumed to + specify the boundary points of quadrilaterals. They should thus + each have one more column and row than the *C* array. For compatibility + with Matlab as well as convenience, the case in which *X*, *Y*, *C* + have the same dimension is also accepted and handled by dropping the + last row and column of *C*. + + If Gouraud shading is used, the arrays *X* and *Y* are assumed to + specify the centers of the quadrilaterals and thus should have the + same dimensions as *C*. If this is not the case an error will be + thrown. However, for compatibility with the flat shading case, the + ``interpolate_grids`` keyword argument is provided. When set to + ``True`` and *X* and *Y* have one row and column more then *C*, + *X* and *Y* will be linearly interpolated to obtain the corresponding + centerpoints. + Keyword arguments: *cmap*: [ *None* | Colormap ] @@ -5536,6 +5581,19 @@ def pcolormesh(self, *args, **kwargs): *alpha*: ``0 <= scalar <= 1`` or *None* the alpha blending value + *interpolate_grids*: [``True`` | ``False``] + + When set to ``True`` and Gouraud shading is used with *X* and + *Y* with one more row and column than *C*, *X* and *Y* will be + linearly interpolated to obtain the centers of the quadrilaterals + described by *X* and *Y*. + + When set to ``False`` the above case with cause an error to be + thrown. + + Ignored when ``shading = flat``. + + Return value is a :class:`matplotlib.collections.QuadMesh` object. @@ -5561,11 +5619,15 @@ def pcolormesh(self, *args, **kwargs): vmax = kwargs.pop('vmax', None) shading = kwargs.pop('shading', 'flat').lower() antialiased = kwargs.pop('antialiased', False) + interpolate_grids = kwargs.pop('interpolate_grids', False) kwargs.setdefault('edgecolors', 'None') allmatch = (shading == 'gouraud') - X, Y, C = self._pcolorargs('pcolormesh', *args, allmatch=allmatch) + X, Y, C = self._pcolorargs('pcolormesh', + *args, + allmatch=allmatch, + interpolate_grids=interpolate_grids) Ny, Nx = X.shape X = X.ravel() Y = Y.ravel() diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index 12c219215024..8b303ac2729a 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -1180,6 +1180,7 @@ def test_pcolorargs(): y = np.linspace(-1.5, 1.5, n*2) X, Y = np.meshgrid(x, y) Z = np.sqrt(X**2 + Y**2)/5 + Z_1 = Z[:-1, :-1] _, ax = plt.subplots() with pytest.raises(TypeError): @@ -1187,9 +1188,14 @@ def test_pcolorargs(): with pytest.raises(TypeError): ax.pcolormesh(X, Y, Z.T) with pytest.raises(TypeError): - ax.pcolormesh(x, y, Z[:-1, :-1], shading="gouraud") + ax.pcolormesh(y, x, Z, shading="gouraud") with pytest.raises(TypeError): - ax.pcolormesh(X, Y, Z[:-1, :-1], shading="gouraud") + ax.pcolormesh(X, Y, Z.T, shading="gouraud") + with pytest.raises(TypeError): + ax.pcolormesh(X, Y, Z_1, shading="gouraud") + + ax.pcolormesh(X, Y, Z_1) + ax.pcolormesh(x, y, Z_1, shading="gouraud", interpolate_grids=True) @image_comparison(baseline_images=['canonical'])