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

Skip to content

Commit 7e84272

Browse files
committed
MNT: Introduce cmap(..., by_index=...) parameter
This allows to explicitly state whether inputs should be handled as normalized values [0, 1] or indices into the colormap. The value `by_index='auto'` is the current default and makes the interpretation dependent on the data type. Resulting in surprising behavior #28198. It is planned to deprecate this in a follow-up PR and switch to `by_index=False` eventually. The standard case of float inputs will not be affected by this change. But users that want to pass indices, will then have to explicitly use `by_index=True`.
1 parent c80e8ae commit 7e84272

File tree

3 files changed

+50
-15
lines changed

3 files changed

+50
-15
lines changed

lib/matplotlib/colors.py

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -752,51 +752,66 @@ def __init__(self, name, N=256, *, bad=None, under=None, over=None):
752752
#: `matplotlib.colorbar.Colorbar` constructor.
753753
self.colorbar_extend = False
754754

755-
def __call__(self, X, alpha=None, bytes=False):
755+
def __call__(self, X, alpha=None, bytes=False, by_index='auto'):
756756
r"""
757757
Parameters
758758
----------
759759
X : float or int or array-like
760-
The data value(s) to convert to RGBA.
761-
For floats, *X* should be in the interval ``[0.0, 1.0]`` to
762-
return the RGBA values ``X*100`` percent along the Colormap line.
763-
For integers, *X* should be in the interval ``[0, Colormap.N)`` to
764-
return RGBA values *indexed* from the Colormap with index ``X``.
760+
The data value(s) to convert to RGBA. The interpretation (normalized
761+
values or index values) depends on *by_index*.
765762
alpha : float or array-like or None
766763
Alpha must be a scalar between 0 and 1, a sequence of such
767764
floats with shape matching X, or None.
768765
bytes : bool, default: False
769766
If False (default), the returned RGBA values will be floats in the
770767
interval ``[0, 1]`` otherwise they will be `numpy.uint8`\s in the
771768
interval ``[0, 255]``.
769+
by_index: bool or 'auto', default: 'auto'
770+
How the input *X* is interpreted:
771+
772+
- If True, *X* is treated as an array of indices into the Colormap
773+
lookup table, i.e. the range ``[0, Colormap.N)`` covers the colormap.
774+
- If False, *X* is treated normalized values, i.e. the range
775+
``[0.0, 1.0]`` covers the colormap.
776+
- If 'auto', the type of *X* is used to determine the interpretation.
777+
float inputs are treated like ``by_index=False``, integer inputs
778+
are treated like ``by_index=True``.
772779
773780
Returns
774781
-------
775782
Tuple of RGBA values if X is scalar, otherwise an array of
776783
RGBA values with a shape of ``X.shape + (4, )``.
777784
"""
778-
rgba, mask = self._get_rgba_and_mask(X, alpha=alpha, bytes=bytes)
785+
rgba, mask = self._get_rgba_and_mask(X, alpha=alpha, bytes=bytes,
786+
by_index=by_index)
779787
if not np.iterable(X):
780788
rgba = tuple(rgba)
781789
return rgba
782790

783-
def _get_rgba_and_mask(self, X, alpha=None, bytes=False):
791+
def _get_rgba_and_mask(self, X, alpha=None, bytes=False, by_index='auto'):
784792
r"""
785793
Parameters
786794
----------
787795
X : float or int or array-like
788-
The data value(s) to convert to RGBA.
789-
For floats, *X* should be in the interval ``[0.0, 1.0]`` to
790-
return the RGBA values ``X*100`` percent along the Colormap line.
791-
For integers, *X* should be in the interval ``[0, Colormap.N)`` to
792-
return RGBA values *indexed* from the Colormap with index ``X``.
796+
The data value(s) to convert to RGBA. The interpretation (normalized
797+
values or index values) depends on *by_index*.
793798
alpha : float or array-like or None
794799
Alpha must be a scalar between 0 and 1, a sequence of such
795800
floats with shape matching X, or None.
796801
bytes : bool, default: False
797802
If False (default), the returned RGBA values will be floats in the
798803
interval ``[0, 1]`` otherwise they will be `numpy.uint8`\s in the
799804
interval ``[0, 255]``.
805+
by_index: bool or 'auto', default: 'auto'
806+
How the input *X* is interpreted:
807+
808+
- If True, *X* is treated as an array of indices into the Colormap
809+
lookup table, i.e. the range ``[0, Colormap.N)`` covers the colormap.
810+
- If False, *X* is treated normalized values, i.e. the range
811+
``[0.0, 1.0]`` covers the colormap.
812+
- If 'auto', the type of *X* is used to determine the interpretation.
813+
float inputs are treated like ``by_index=False``, integer inputs
814+
are treated like ``by_index=True``.
800815
801816
Returns
802817
-------
@@ -812,7 +827,7 @@ def _get_rgba_and_mask(self, X, alpha=None, bytes=False):
812827
if not xa.dtype.isnative:
813828
# Native byteorder is faster.
814829
xa = xa.byteswap().view(xa.dtype.newbyteorder())
815-
if xa.dtype.kind == "f":
830+
if by_index is False or (by_index == 'auto' and xa.dtype.kind == "f"):
816831
xa *= self.N
817832
# xa == 1 (== N after multiplication) is not out of range.
818833
xa[xa == self.N] = self.N - 1

lib/matplotlib/colors.pyi

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,11 @@ class Colormap:
8080
) -> None: ...
8181
@overload
8282
def __call__(
83-
self, X: Sequence[float] | np.ndarray, alpha: ArrayLike | None = ..., bytes: bool = ...
83+
self,
84+
X: Sequence[float] | np.ndarray,
85+
alpha: ArrayLike | None = ...,
86+
bytes: bool = ...,
87+
by_index: bool | Literal['auto'] = ...,
8488
) -> np.ndarray: ...
8589
@overload
8690
def __call__(

lib/matplotlib/tests/test_colors.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,22 @@ def test_colormap_invalid():
203203
assert_array_equal(cmap(np.nan), [0., 0., 0., 0.])
204204

205205

206+
def test_colormap_by_index():
207+
cmap = mpl.colormaps["plasma"]
208+
N = cmap.N
209+
assert_array_equal(cmap([0., 0.5, 1.]), cmap([0., 0.5, 1.], by_index=False))
210+
# auto-detection based on input type by default
211+
assert_array_equal(cmap([0., 0.5, 1.]), cmap([0, N//2, N]))
212+
# by_index=True forces floats as index interpretation
213+
assert_array_equal(cmap([0., N/2, float(N)], by_index=True), cmap([0, N//2, N]))
214+
215+
cmap = mpl.colormaps["plasma"].with_extremes(over='r', under='b', bad='g')
216+
217+
assert cmap(1) == cmap(1/N)
218+
assert cmap(1., by_index=True) == cmap(1/N)
219+
assert cmap(1, by_index=False) == cmap(1.)
220+
221+
206222
def test_colormap_return_types():
207223
"""
208224
Make sure that tuples are returned for scalar input and

0 commit comments

Comments
 (0)