Skip to content
Open
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
52 changes: 52 additions & 0 deletions doc/release/next_whats_new/pcolormesh_gouraud.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
``gouraud`` shading supports data at quadrilaterals centers
-----------------------------------------------------------

`~.Axes.pcolormesh` previously required data at the corners of
quadrilaterals for ``gouraud`` shading. It now also supports data
defined at the centers of quadrilaterals.

This now allows `~.Axes.pcolormesh` to provide both constant and
linearly interpolated shading for each data location.

=============== ======== =====================
.. constant linearly interpolated
=============== ======== =====================
data at corners nearest gouraud
data at centers flat gouraud
=============== ======== =====================

Shading can be switched for the same data location, allowing
switches between ``nearest`` and ``gouraud``, and now also
between ``flat`` and ``gouraud``.

For example:

.. plot::
:include-source: true
:alt: Switching between constant and linearly interpolated shading for data at corners and centers.

import matplotlib.pyplot as plt
import numpy as np

nrows, ncols = 3, 5
Z = np.arange(nrows * ncols).reshape(nrows, ncols)
x = np.arange(ncols + 1)
y = np.arange(nrows + 1)

fig, axs = plt.subplots(2, 2, layout='constrained')

# Data at corners, requires X and Y the same shape as Z.
axs[0, 0].pcolormesh(x[:-1], y[:-1], Z, shading='nearest')
axs[0, 0].set_title('nearest: X, Y, Z same shape')

axs[0, 1].pcolormesh(x[:-1], y[:-1], Z, shading='gouraud')
axs[0, 1].set_title('gouraud: X, Y, Z same shape')

# Data at centers, requires X and Y one larger than Z.
axs[1, 0].pcolormesh(x, y, Z, shading='flat')
axs[1, 0].set_title('flat: X, Y one larger than Z')

axs[1, 1].pcolormesh(x, y, Z, shading='gouraud')
axs[1, 1].set_title('gouraud: X, Y one larger than Z')

plt.show()
Original file line number Diff line number Diff line change
Expand Up @@ -108,14 +108,30 @@ def _annotate(ax, x, y, title):
#
# `Gouraud shading <https://en.wikipedia.org/wiki/Gouraud_shading>`_ can also
# be specified, where the color in the quadrilaterals is linearly interpolated
# between the grid points. The shapes of *X*, *Y*, *Z* must be the same.
# between the grid points. The data is specified at the corners of the
# quadrilaterals, in which case *X*, *Y* and *Z* are all the same shape.

fig, ax = plt.subplots(layout='constrained')
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")

# %%
# Gouraud Shading, one larger grid
# --------------------------------
#
# In some cases, the user has data defined at the centers of the quadrilaterals
# with *X* and *Y* one larger than *Z*. ``shading='gouraud'`` also supports
# this by using the grid quadrilateral centers as the corners of each colored
# quadrilateral.

fig, ax = plt.subplots(layout='constrained')
x = np.arange(ncols + 1)
y = np.arange(nrows + 1)
ax.pcolormesh(x, y, Z, shading='gouraud', vmin=Z.min(), vmax=Z.max())
_annotate(ax, x, y, "shading='gouraud'; X, Y one larger than Z")

plt.show()
# %%
#
Expand Down
41 changes: 28 additions & 13 deletions lib/matplotlib/axes/_axes.py
Original file line number Diff line number Diff line change
Expand Up @@ -6523,9 +6523,13 @@ def _pcolorargs(self, funcname, *args, shading='auto', **kwargs):
f" see help({funcname})")
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))
if shading == 'gouraud' and (Nx, Ny) == (ncols + 1, nrows + 1):
# the center of each quad is the average of its four corners
X = 0.25 * (X[:-1, :-1] + X[:-1, 1:] + X[1:, 1:] + X[1:, :-1])
Y = 0.25 * (Y[:-1, :-1] + Y[:-1, 1:] + Y[1:, 1:] + Y[1:, :-1])
else:
raise TypeError(f"Dimensions of C {C.shape} are incompatible with"
f" X ({Nx}) and/or Y ({Ny}); see help({funcname})")
if shading == 'nearest':
# grid is specified at the center, so define corners
# at the midpoints between the grid centers and then use the
Expand Down Expand Up @@ -6818,11 +6822,17 @@ def pcolormesh(self, *args, alpha=None, norm=None, cmap=None, vmin=None,
greater than those of *C*, otherwise a TypeError is raised. The
quadrilateral is colored due to the value at ``C[i, j]``.

If ``shading='nearest'`` or ``'gouraud'``, the dimensions of *X*
and *Y* should be the same as those of *C* (if not, a TypeError
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 carried out between the quadrilateral corners.
If ``shading='nearest'`` the dimensions of *X* and *Y* should be
the same as those of *C*, otherwise a TypeError is raised. The
color ``C[i, j]`` is centered on ``(X[i, j], Y[i, j])``.

If ``shading='gouraud'`` the dimensions of *X* and *Y* should be
the same as those of *C* or be one greater than those of *C*,
otherwise a TypeError is raised. If the dimensions of *X* and *Y*
are one greater, they are internally converted to match the shape
of *C* by replacing each quadrilateral with a point at its center,
computed as the average of their four corners. In both cases a
smooth interpolation is carried 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 2D arrays, making a
Expand Down Expand Up @@ -6861,11 +6871,16 @@ def pcolormesh(self, *args, alpha=None, norm=None, cmap=None, vmin=None,
- '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.
The dimensions of *X* and *Y* must be the same as *C*. When
Gouraud shading is used, *edgecolors* is ignored.
- 'gouraud': If the mesh data is defined at the corners of grid
quadrilaterals, with *X*, *Y* and *C* having the same dimensions,
each grid quad will be Gouraud shaded. If the color values
are specified at the centers of grid quadrilaterals, *X* and *Y*
have dimensions one greater than those of *C*, and each colored
quadrilateral will use the grid quad centers as its corners, so
that it can be Gouraud shaded: The color of the corners (i', j')
are given by ``C[i', j']``, and the color values of the area
in between are interpolated from the corner values.
When Gouraud shading is used, *edgecolors* is ignored.
- 'auto': Choose 'flat' if dimensions of *X* and *Y* are one
larger than *C*. Choose 'nearest' if dimensions are the same.

Expand Down
38 changes: 36 additions & 2 deletions lib/matplotlib/tests/test_axes.py
Original file line number Diff line number Diff line change
Expand Up @@ -1623,6 +1623,40 @@ def test_pcolor_log_scale(fig_test, fig_ref):
ax.set_xscale('log')


def test_pcolormesh_switching_shadings():
nrows, ncols = 3, 4
Z = np.arange(nrows * ncols).reshape(nrows, ncols)
x = np.arange(ncols + 1)
y = np.arange(nrows + 1)

_, ax = plt.subplots()

ax.pcolormesh(x, y, Z, shading='flat')
ax.pcolormesh(x, y, Z, shading='gouraud')
with pytest.raises(TypeError):
ax.pcolormesh(x, y, Z, shading='nearest')

ax.pcolormesh(x[:-1], y[:-1], Z, shading='nearest')
ax.pcolormesh(x[:-1], y[:-1], Z, shading='gouraud')
with pytest.raises(TypeError):
ax.pcolormesh(x[:-1], y[:-1], Z, shading='flat')


@check_figures_equal()
def test_pcolormesh_gouraud_grid_conversion(fig_test, fig_ref):
Z = np.arange(6).reshape(2, 3)

x_test = np.array([0, 2, 8, 12])
y_test = np.array([0, 2, 6])
ax_test = fig_test.subplots()
ax_test.pcolormesh(x_test, y_test, Z, shading='gouraud')

x_ref = np.array([1, 5, 10])
y_ref = np.array([1, 4])
ax_ref = fig_ref.subplots()
ax_ref.pcolormesh(x_ref, y_ref, Z, shading='gouraud')


def test_pcolorargs():
n = 12
x = np.linspace(-1.5, 1.5, n)
Expand All @@ -1636,9 +1670,9 @@ 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(x, y, Z[:-2, :-2], shading='gouraud')
with pytest.raises(TypeError):
ax.pcolormesh(X, Y, Z[:-1, :-1], shading="gouraud")
ax.pcolormesh(X, Y, Z[:-2, :-2], shading='gouraud')
x[0] = np.nan
with pytest.raises(ValueError):
ax.pcolormesh(x, y, Z[:-1, :-1])
Expand Down
Loading