Thanks to visit codestin.com
Credit goes to github.com

Skip to content

ENH: Allow RGB(A) arrays for pcolormesh #24619

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

Merged
merged 2 commits into from
Dec 5, 2022
Merged
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
16 changes: 16 additions & 0 deletions doc/users/next_whats_new/rgba_pcolormesh.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
``pcolormesh`` accepts RGB(A) colors
------------------------------------

The `~.Axes.pcolormesh` method can now handle explicit colors
specified with RGB(A) values. To specify colors, the array must be 3D
with a shape of ``(M, N, [3, 4])``.

.. plot::
:include-source: true

import matplotlib.pyplot as plt
import numpy as np

colors = np.linspace(0, 1, 90).reshape((5, 6, 3))
plt.pcolormesh(colors)
plt.show()
32 changes: 19 additions & 13 deletions lib/matplotlib/axes/_axes.py
Original file line number Diff line number Diff line change
Expand Up @@ -5679,7 +5679,7 @@ def _pcolorargs(self, funcname, *args, shading='auto', **kwargs):

if len(args) == 1:
C = np.asanyarray(args[0])
nrows, ncols = C.shape
nrows, ncols = C.shape[:2]
if shading in ['gouraud', 'nearest']:
X, Y = np.meshgrid(np.arange(ncols), np.arange(nrows))
else:
Expand Down Expand Up @@ -5708,7 +5708,7 @@ def _pcolorargs(self, funcname, *args, shading='auto', **kwargs):
X = X.data # strip mask as downstream doesn't like it...
if isinstance(Y, np.ma.core.MaskedArray):
Y = Y.data
nrows, ncols = C.shape
nrows, ncols = C.shape[:2]
else:
raise _api.nargs_error(funcname, takes="1 or 3", given=len(args))

Expand Down Expand Up @@ -6045,9 +6045,18 @@ def pcolormesh(self, *args, alpha=None, norm=None, cmap=None, vmin=None,

Parameters
----------
C : 2D array-like
The color-mapped values. Color-mapping is controlled by *cmap*,
*norm*, *vmin*, and *vmax*.
C : array-like
The mesh data. Supported array shapes are:

- (M, N) or M*N: a mesh with scalar data. The values are mapped to
colors using normalization and a colormap. See parameters *norm*,
*cmap*, *vmin*, *vmax*.
- (M, N, 3): an image with RGB values (0-1 float or 0-255 int).
- (M, N, 4): an image with RGBA values (0-1 float or 0-255 int),
i.e. including transparency.

The first two dimensions (M, N) define the rows and columns of
the mesh data.

X, Y : array-like, optional
The coordinates of the corners of quadrilaterals of a pcolormesh::
Expand Down Expand Up @@ -6207,8 +6216,9 @@ def pcolormesh(self, *args, alpha=None, norm=None, cmap=None, vmin=None,
X, Y, C, shading = self._pcolorargs('pcolormesh', *args,
shading=shading, kwargs=kwargs)
coords = np.stack([X, Y], axis=-1)
# convert to one dimensional array
C = C.ravel()
# convert to one dimensional array, except for 3D RGB(A) arrays
if C.ndim != 3:
C = C.ravel()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do 3-D arrays not need to be flattened at all?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

or conversely, why are we flattening 2-D arrays?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I am not a fan of the flattening either... Allowing 2-D arrays is pretty new (only the last couple of releases), so everyone always used to have to flatten their arrays before getting/setting arrays. Unfortunately, that is baked into a few of our tests as expecting to pass a 2-D array in, and then calling mesh = plt.pcolormesh(z2d); mesh.get_array() == z2d.ravel(). I don't think we should be messing with people's arrays if they pass them in as 2D, but that may be too controversial to change at this point too...

Copy link
Contributor Author

@greglucas greglucas Dec 5, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do 3-D arrays not need to be flattened at all?

Specifically for 3-D, we can not flatten them because the image RGB(A) color handling code will only handle the 3-D shapes of (M, N, [3 or 4])


kwargs.setdefault('snap', mpl.rcParams['pcolormesh.snap'])

Expand Down Expand Up @@ -6384,14 +6394,10 @@ def pcolorfast(self, *args, alpha=None, norm=None, cmap=None, vmin=None,
if style == "quadmesh":
# data point in each cell is value at lower left corner
coords = np.stack([x, y], axis=-1)
if np.ndim(C) == 2:
qm_kwargs = {"array": np.ma.ravel(C)}
elif np.ndim(C) == 3:
qm_kwargs = {"color": np.ma.reshape(C, (-1, C.shape[-1]))}
else:
if np.ndim(C) not in {2, 3}:
raise ValueError("C must be 2D or 3D")
collection = mcoll.QuadMesh(
coords, **qm_kwargs,
coords, array=C,
alpha=alpha, cmap=cmap, norm=norm,
antialiased=False, edgecolors="none")
self.add_collection(collection, autolim=False)
Expand Down
23 changes: 20 additions & 3 deletions lib/matplotlib/collections.py
Original file line number Diff line number Diff line change
Expand Up @@ -1955,7 +1955,16 @@ def set_array(self, A):

Parameters
----------
A : (M, N) array-like or M*N array-like
A : array-like
The mesh data. Supported array shapes are:

- (M, N) or M*N: a mesh with scalar data. The values are mapped to
colors using normalization and a colormap. See parameters *norm*,
*cmap*, *vmin*, *vmax*.
- (M, N, 3): an image with RGB values (0-1 float or 0-255 int).
- (M, N, 4): an image with RGBA values (0-1 float or 0-255 int),
i.e. including transparency.

If the values are provided as a 2D grid, the shape must match the
coordinates grid. If the values are 1D, they are reshaped to 2D.
M, N follow from the coordinates grid, where the coordinates grid
Expand All @@ -1976,11 +1985,19 @@ def set_array(self, A):
if len(shape) == 1:
if shape[0] != (h*w):
faulty_data = True
elif shape != (h, w):
if np.prod(shape) == (h * w):
elif shape[:2] != (h, w):
if np.prod(shape[:2]) == (h * w):
misshapen_data = True
else:
faulty_data = True
elif len(shape) == 3 and shape[2] not in {3, 4}:
# 3D data must be RGB(A) (h, w, [3,4])
# the (h, w) check is taken care of above
raise ValueError(
f"For X ({width}) and Y ({height}) with "
f"{self._shading} shading, the expected shape of "
f"A with RGB(A) colors is ({h}, {w}, [3 or 4]), not "
f"{A.shape}")

if misshapen_data:
raise ValueError(
Expand Down
11 changes: 11 additions & 0 deletions lib/matplotlib/tests/test_axes.py
Original file line number Diff line number Diff line change
Expand Up @@ -1298,6 +1298,17 @@ def test_pcolormesh_alpha():
ax4.pcolormesh(Qx, Qy, Z, cmap=cmap, shading='gouraud', zorder=1)


@pytest.mark.parametrize("dims,alpha", [(3, 1), (4, 0.5)])
@check_figures_equal(extensions=["png"])
def test_pcolormesh_rgba(fig_test, fig_ref, dims, alpha):
ax = fig_test.subplots()
c = np.ones((5, 6, dims), dtype=float) / 2
ax.pcolormesh(c)

ax = fig_ref.subplots()
ax.pcolormesh(c[..., 0], cmap="gray", vmin=0, vmax=1, alpha=alpha)


@image_comparison(['pcolormesh_datetime_axis.png'], style='mpl20')
def test_pcolormesh_datetime_axis():
# Remove this line when this test image is regenerated.
Expand Down
21 changes: 21 additions & 0 deletions lib/matplotlib/tests/test_collections.py
Original file line number Diff line number Diff line change
Expand Up @@ -830,6 +830,24 @@ def test_quadmesh_set_array_validation():
r"are incompatible with X \(11\) and/or Y \(8\)"):
coll.set_array(z.ravel())

# RGB(A) tests
z = np.ones((9, 6, 3)) # RGB with wrong X/Y dims
with pytest.raises(TypeError, match=r"Dimensions of A \(9, 6, 3\) "
r"are incompatible with X \(11\) and/or Y \(8\)"):
coll.set_array(z)

z = np.ones((9, 6, 4)) # RGBA with wrong X/Y dims
with pytest.raises(TypeError, match=r"Dimensions of A \(9, 6, 4\) "
r"are incompatible with X \(11\) and/or Y \(8\)"):
coll.set_array(z)

z = np.ones((7, 10, 2)) # Right X/Y dims, bad 3rd dim
with pytest.raises(ValueError, match=r"For X \(11\) and Y \(8\) with "
r"flat shading, the expected shape of "
r"A with RGB\(A\) colors is \(7, 10, \[3 or 4\]\), "
r"not \(7, 10, 2\)"):
coll.set_array(z)

x = np.arange(10)
y = np.arange(7)
z = np.random.random((7, 10))
Expand Down Expand Up @@ -1048,6 +1066,9 @@ def test_array_wrong_dimensions():
pc = plt.pcolormesh(z)
pc.set_array(z) # 2D is OK for Quadmesh
pc.update_scalarmappable()
# 3D RGB is OK as well
z = np.arange(36).reshape(3, 4, 3)
pc.set_array(z)


def test_get_segments():
Expand Down