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
191 changes: 126 additions & 65 deletions lib/matplotlib/axes/_axes.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
import functools
import itertools
import logging
Expand Down Expand Up @@ -6462,23 +6462,52 @@
# - reset shading if shading='auto' to flat or nearest
# depending on size;

# Check and normalize shading parameter:
if isinstance(shading, str):
shading = shading.lower()
shading_x = shading_y = shading
else:
try:
shading_x, shading_y = shading
if isinstance(shading_x, str):
shading_x = shading_x.lower()
if isinstance(shading_y, str):
shading_y = shading_y.lower()
except (ValueError, TypeError) as e:
raise ValueError(
"shading must be a string or a 2-tuple of strings"
) from e

_valid_shading = ['gouraud', 'nearest', 'flat', 'auto']
try:
_api.check_in_list(_valid_shading, shading=shading)
except ValueError:
_api.warn_external(f"shading value '{shading}' not in list of "
f"valid values {_valid_shading}. Setting "
"shading='auto'.")
shading = 'auto'
_api.check_in_list(_valid_shading, shading_x=shading_x)
_api.check_in_list(_valid_shading, shading_y=shading_y)

if (
(shading_x == 'gouraud' or shading_y == 'gouraud')
and shading_x != shading_y
):
raise ValueError(
"shading='gouraud' cannot be mixed with other shading types."
)

if len(args) == 1:
C = np.asanyarray(args[0])
nrows, ncols = C.shape[:2]
if shading in ['gouraud', 'nearest']:
X, Y = np.meshgrid(np.arange(ncols), np.arange(nrows))

grid_shading_x = 'flat' if shading_x == 'auto' else shading_x
grid_shading_y = 'flat' if shading_y == 'auto' else shading_y

if grid_shading_x in ['gouraud', 'nearest']:
x_grid = np.arange(ncols)
else:
X, Y = np.meshgrid(np.arange(ncols + 1), np.arange(nrows + 1))
shading = 'flat'
x_grid = np.arange(ncols + 1)

if grid_shading_y in ['gouraud', 'nearest']:
y_grid = np.arange(nrows)
else:
y_grid = np.arange(nrows + 1)

X, Y = np.meshgrid(x_grid, y_grid)
elif len(args) == 3:
# Check x and y for bad data...
C = np.asanyarray(args[2])
Expand Down Expand Up @@ -6509,61 +6538,81 @@
raise TypeError(f'Incompatible X, Y inputs to {funcname}; '
f'see help({funcname})')

if shading == 'auto':
if ncols == Nx and nrows == Ny:
shading = 'nearest'
if shading_x == 'auto':
if ncols == Nx:
shading_x = 'nearest'
else:
shading = 'flat'

if shading == 'flat':
if (Nx, Ny) != (ncols + 1, nrows + 1):
raise TypeError(f"Dimensions of C {C.shape} should"
f" be one smaller than X({Nx}) and Y({Ny})"
f" while using shading='flat'"
f" see help({funcname})")
else: # ['nearest', 'gouraud']:
shading_x = 'flat'
if shading_y == 'auto':
if nrows == Ny:
shading_y = 'nearest'
else:
shading_y = 'flat'

if shading_x == '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 == 'nearest':
# 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, require_monotonicity=False):
# helper for below. To ensure the cell edges are calculated
# correctly, when expanding columns, the monotonicity of
# X coords needs to be checked. When expanding rows, the
# monotonicity of Y coords needs to be checked.
if np.shape(X)[1] > 1:
dX = np.diff(X, axis=1) * 0.5
if (require_monotonicity and
not (np.all(dX >= 0) or np.all(dX <= 0))):
_api.warn_external(
f"The input coordinates to {funcname} are "
"interpreted as cell centers, but are not "
"monotonically increasing or decreasing. "
"This may lead to incorrectly calculated cell "
"edges, in which case, please supply "
f"explicit cell edges to {funcname}.")

hstack = np.ma.hstack if np.ma.isMA(X) else np.hstack
X = 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, require_monotonicity=True)
Y = _interp_grid(Y)
if nrows == Ny:
X = _interp_grid(X.T).T
Y = _interp_grid(Y.T, require_monotonicity=True).T
shading = 'flat'
shading = 'gouraud'
else:
if shading_x == 'flat' and Nx != ncols + 1:
raise TypeError(f"Dimensions of C {C.shape} should be one smaller than "
f"X ({Nx}) along the X-axis while using shading='flat'")
if shading_x == 'nearest' and Nx != ncols:
raise TypeError(
f"Dimensions of C {C.shape} should be equal to "
f"X ({Nx}) along the X-axis while using "
"shading='nearest'"
)

if shading_y == 'flat' and Ny != nrows + 1:
raise TypeError(f"Dimensions of C {C.shape} should be one smaller than "
f"Y ({Ny}) along the Y-axis while using shading='flat'")
if shading_y == 'nearest' and Ny != nrows:
raise TypeError(
f"Dimensions of C {C.shape} should be equal to "
f"Y ({Ny}) along the Y-axis while using "
"shading='nearest'"
)

# 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, require_monotonicity=False):
# helper for below. To ensure the cell edges are calculated
# correctly, when expanding columns, the monotonicity of
# X coords needs to be checked. When expanding rows, the
# monotonicity of Y coords needs to be checked.
if np.shape(X)[1] > 1:
dX = np.diff(X, axis=1) * 0.5
if (require_monotonicity and
not (np.all(dX >= 0) or np.all(dX <= 0))):
_api.warn_external(
f"The input coordinates to {funcname} are "
"interpreted as cell centers, but are not "
"monotonically increasing or decreasing. "
"This may lead to incorrectly calculated cell "
"edges, in which case, please supply "
f"explicit cell edges to {funcname}.")

hstack = np.ma.hstack if np.ma.isMA(X) else np.hstack
X = 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 shading_x == 'nearest':
X = _interp_grid(X, require_monotonicity=True)
Y = _interp_grid(Y)
if shading_y == 'nearest':
X = _interp_grid(X.T).T
Y = _interp_grid(Y.T, require_monotonicity=True).T
shading = 'flat'

C = cbook.safe_masked_invalid(C, copy=True)
return X, Y, C, shading
Expand Down Expand Up @@ -6623,7 +6672,7 @@
expanded as needed into the appropriate 2D arrays, making a
rectangular grid.

shading : {'flat', 'nearest', 'auto'}, default: :rc:`pcolor.shading`
shading : {'flat', 'nearest', 'auto'} or 2-tuple, default: :rc:`pcolor.shading`
The fill style for the quadrilateral. Possible values:

- 'flat': A solid color is used for each quad. The color of the
Expand All @@ -6636,6 +6685,10 @@
- 'auto': Choose 'flat' if dimensions of *X* and *Y* are one
larger than *C*. Choose 'nearest' if dimensions are the same.

Additionally, a 2-tuple of strings `(shading_x, shading_y)` can be
passed to specify different shading types for the X and Y axes
individually.

See :doc:`/gallery/images_contours_and_fields/pcolormesh_grids`
for more description.

Expand Down Expand Up @@ -6717,7 +6770,8 @@

if shading is None:
shading = mpl.rcParams['pcolor.shading']
shading = shading.lower()
if isinstance(shading, str):
shading = shading.lower()
X, Y, C, shading = self._pcolorargs('pcolor', *args, shading=shading,
kwargs=kwargs)
linewidths = (0.25,)
Expand Down Expand Up @@ -6850,7 +6904,7 @@
alpha : float, default: None
The alpha blending value, between 0 (transparent) and 1 (opaque).

shading : {'flat', 'nearest', 'gouraud', 'auto'}, optional
shading : {'flat', 'nearest', 'gouraud', 'auto'} or 2-tuple, optional
The fill style for the quadrilateral; defaults to
:rc:`pcolor.shading`. Possible values:

Expand All @@ -6869,6 +6923,11 @@
- 'auto': Choose 'flat' if dimensions of *X* and *Y* are one
larger than *C*. Choose 'nearest' if dimensions are the same.

Additionally, a 2-tuple of strings `(shading_x, shading_y)` can be
passed to specify different shading types for the X and Y axes
individually. Note that 'gouraud' cannot be mixed with other shading
types.

See :doc:`/gallery/images_contours_and_fields/pcolormesh_grids`
for more description.

Expand Down Expand Up @@ -6953,7 +7012,9 @@
`~.Axes.pcolormesh`, which is not available with `~.Axes.pcolor`.

"""
shading = mpl._val_or_rc(shading, 'pcolor.shading').lower()
shading = mpl._val_or_rc(shading, 'pcolor.shading')
if isinstance(shading, str):
shading = shading.lower()
kwargs.setdefault('edgecolors', 'none')

X, Y, C, shading = self._pcolorargs('pcolormesh', *args,
Expand Down
14 changes: 12 additions & 2 deletions lib/matplotlib/axes/_axes.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -526,7 +526,12 @@ class Axes(_AxesBase):
def pcolor(
self,
*args: ArrayLike,
shading: Literal["flat", "nearest", "auto"] | None = ...,
shading: Literal["flat", "nearest", "auto"]
| tuple[
Literal["flat", "nearest", "auto"],
Literal["flat", "nearest", "auto"],
]
| None = ...,
alpha: float | None = ...,
norm: str | Normalize | None = ...,
cmap: str | Colormap | None = ...,
Expand All @@ -545,7 +550,12 @@ class Axes(_AxesBase):
vmin: float | None = ...,
vmax: float | None = ...,
colorizer: Colorizer | None = ...,
shading: Literal["flat", "nearest", "gouraud", "auto"] | None = ...,
shading: Literal["flat", "nearest", "gouraud", "auto"]
| tuple[
Literal["flat", "nearest", "gouraud", "auto"],
Literal["flat", "nearest", "gouraud", "auto"],
]
| None = ...,
antialiased: bool = ...,
data: DataParamType = ...,
**kwargs
Expand Down
42 changes: 41 additions & 1 deletion lib/matplotlib/rcsetup.py
Original file line number Diff line number Diff line change
Expand Up @@ -979,6 +979,46 @@ def validate_hist_bins(s):
" a sequence of floats")


def validate_pcolor_shading(s):
valid = ["auto", "flat", "nearest", "gouraud"]
if isinstance(s, str):
s = s.lower()
if s in valid:
return s
raise ValueError(f"shading {s!r} must be one of {valid}")
# Otherwise, check if it's an iterable of size 2
try:
s_tuple = tuple(s)
except TypeError as e:
raise ValueError(
f"shading {s!r} must be a string or a 2-tuple of strings"
) from e
if len(s_tuple) != 2:
raise ValueError(
f"shading {s!r} must be a string or a 2-tuple of strings"
)

normalized = []
for item in s_tuple:
if isinstance(item, str):
item = item.lower()
if item in valid:
normalized.append(item)
continue
raise ValueError(
f"shading components must be one of {valid}, got {item!r}"
)
if (
(normalized[0] == 'gouraud' or normalized[1] == 'gouraud')
and normalized[0] != normalized[1]
):
raise ValueError(
"shading='gouraud' cannot be mixed with other shading types."
)
return tuple(normalized)



class _ignorecase(list):
"""A marker class indicating that a list-of-str is case-insensitive."""

Expand Down Expand Up @@ -1032,7 +1072,7 @@ def _convert_validator_spec(key, conv):
"markers.fillstyle": validate_fillstyle,

## pcolor(mesh) props:
"pcolor.shading": ["auto", "flat", "nearest", "gouraud"],
"pcolor.shading": validate_pcolor_shading,
"pcolormesh.snap": validate_bool,

## patch props
Expand Down
9 changes: 9 additions & 0 deletions lib/matplotlib/rcsetup.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,15 @@ def validate_hist_bins(
) -> Literal["auto", "sturges", "fd", "doane", "scott", "rice", "sqrt"] | int | list[
float
]: ...
def validate_pcolor_shading(
s: Any,
) -> (
Literal["auto", "flat", "nearest", "gouraud"]
| tuple[
Literal["auto", "flat", "nearest", "gouraud"],
Literal["auto", "flat", "nearest", "gouraud"],
]
): ...

# At runtime is added in __init__.py
defaultParams: dict[str, Any]
Loading
Loading