Skip to content

Fix inconsistent behavior in pcolormesh with Gouraud shading #9594

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions doc/users/whats_new.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
-------------------------------------------

Expand Down
102 changes: 82 additions & 20 deletions lib/matplotlib/axes/_axes.py
Original file line number Diff line number Diff line change
Expand Up @@ -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])
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 ]
Expand Down Expand Up @@ -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.

Expand All @@ -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()
Expand Down
10 changes: 8 additions & 2 deletions lib/matplotlib/tests/test_axes.py
Original file line number Diff line number Diff line change
Expand Up @@ -1180,16 +1180,22 @@ 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):
ax.pcolormesh(y, x, Z)
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'])
Expand Down