From 08fec0d401bd37b082265e67e8cba7e71f5ed4de Mon Sep 17 00:00:00 2001 From: j1642 <60148902+j1642@users.noreply.github.com> Date: Mon, 30 Jan 2023 13:04:39 -0500 Subject: [PATCH] ENH: Add new color spec, tuple (matplotlib_color, alpha) --- .../next_whats_new/new_color_spec_tuple.rst | 21 ++++++++ galleries/examples/color/set_alpha.py | 53 +++++++++++++++++++ galleries/users_explain/colors/colors.py | 3 ++ lib/matplotlib/colors.py | 19 ++++++- lib/matplotlib/tests/test_colors.py | 45 ++++++++++++++++ 5 files changed, 139 insertions(+), 2 deletions(-) create mode 100644 doc/users/next_whats_new/new_color_spec_tuple.rst create mode 100644 galleries/examples/color/set_alpha.py diff --git a/doc/users/next_whats_new/new_color_spec_tuple.rst b/doc/users/next_whats_new/new_color_spec_tuple.rst new file mode 100644 index 000000000000..9f8d0ecabc3e --- /dev/null +++ b/doc/users/next_whats_new/new_color_spec_tuple.rst @@ -0,0 +1,21 @@ +Add a new valid color format ``(matplotlib_color, alpha)`` +---------------------------------------------------------- + + +.. plot:: + :include-source: true + + import matplotlib.pyplot as plt + from matplotlib.patches import Rectangle + + fig, ax = plt.subplots() + + rectangle = Rectangle((.2, .2), .6, .6, + facecolor=('blue', 0.2), + edgecolor=('green', 0.5)) + ax.add_patch(rectangle) + + +Users can define a color using the new color specification, *(matplotlib_color, alpha)*. +Note that an explicit alpha keyword argument will override an alpha value from +*(matplotlib_color, alpha)*. diff --git a/galleries/examples/color/set_alpha.py b/galleries/examples/color/set_alpha.py new file mode 100644 index 000000000000..4130fe1109ef --- /dev/null +++ b/galleries/examples/color/set_alpha.py @@ -0,0 +1,53 @@ +""" +================================= +Ways to set a color's alpha value +================================= + +Compare setting alpha by the *alpha* keyword argument and by one of the Matplotlib color +formats. Often, the *alpha* keyword is the only tool needed to add transparency to a +color. In some cases, the *(matplotlib_color, alpha)* color format provides an easy way +to fine-tune the appearance of a Figure. + +""" + +import matplotlib.pyplot as plt +import numpy as np + +# Fixing random state for reproducibility. +np.random.seed(19680801) + +fig, (ax1, ax2) = plt.subplots(ncols=2, figsize=(8, 4)) + +x_values = [n for n in range(20)] +y_values = np.random.randn(20) + +facecolors = ['green' if y > 0 else 'red' for y in y_values] +edgecolors = facecolors + +ax1.bar(x_values, y_values, color=facecolors, edgecolor=edgecolors, alpha=0.5) +ax1.set_title("Explicit 'alpha' keyword value\nshared by all bars and edges") + + +# Normalize y values to get distinct face alpha values. +abs_y = [abs(y) for y in y_values] +face_alphas = [n / max(abs_y) for n in abs_y] +edge_alphas = [1 - alpha for alpha in face_alphas] + +colors_with_alphas = list(zip(facecolors, face_alphas)) +edgecolors_with_alphas = list(zip(edgecolors, edge_alphas)) + +ax2.bar(x_values, y_values, color=colors_with_alphas, + edgecolor=edgecolors_with_alphas) +ax2.set_title('Normalized alphas for\neach bar and each edge') + +plt.show() + +# %% +# +# .. admonition:: References +# +# The use of the following functions, methods, classes and modules is shown +# in this example: +# +# - `matplotlib.axes.Axes.bar` +# - `matplotlib.pyplot.subplots` diff --git a/galleries/users_explain/colors/colors.py b/galleries/users_explain/colors/colors.py index bd8cdb299a85..d308f0fb1859 100644 --- a/galleries/users_explain/colors/colors.py +++ b/galleries/users_explain/colors/colors.py @@ -68,6 +68,9 @@ | to black if cycle does not | | | include color. | | +--------------------------------------+--------------------------------------+ +| Tuple of one of the above color | - ``('green', 0.3)`` | +| formats and an alpha float. | - ``('#f00', 0.9)`` | ++--------------------------------------+--------------------------------------+ .. _xkcd color survey: https://xkcd.com/color/rgb/ diff --git a/lib/matplotlib/colors.py b/lib/matplotlib/colors.py index 304eccca1bd7..d9a60afd0f3f 100644 --- a/lib/matplotlib/colors.py +++ b/lib/matplotlib/colors.py @@ -315,6 +315,13 @@ def _to_rgba_no_colorcycle(c, alpha=None): *alpha* is ignored for the color value ``"none"`` (case-insensitive), which always maps to ``(0, 0, 0, 0)``. """ + if isinstance(c, tuple) and len(c) == 2: + if alpha is None: + c, alpha = c + else: + c = c[0] + if alpha is not None and not 0 <= alpha <= 1: + raise ValueError("'alpha' must be between 0 and 1, inclusive") orig_c = c if c is np.ma.masked: return (0., 0., 0., 0.) @@ -425,6 +432,11 @@ def to_rgba_array(c, alpha=None): (n, 4) array of RGBA colors, where each channel (red, green, blue, alpha) can assume values between 0 and 1. """ + if isinstance(c, tuple) and len(c) == 2: + if alpha is None: + c, alpha = c + else: + c = c[0] # Special-case inputs that are already arrays, for performance. (If the # array has the wrong kind or shape, raise the error during one-at-a-time # conversion.) @@ -464,9 +476,12 @@ def to_rgba_array(c, alpha=None): return np.array([to_rgba(c, a) for a in alpha], float) else: return np.array([to_rgba(c, alpha)], float) - except (ValueError, TypeError): + except TypeError: pass - + except ValueError as e: + if e.args == ("'alpha' must be between 0 and 1, inclusive", ): + # ValueError is from _to_rgba_no_colorcycle(). + raise e if isinstance(c, str): raise ValueError(f"{c!r} is not a valid color value.") diff --git a/lib/matplotlib/tests/test_colors.py b/lib/matplotlib/tests/test_colors.py index 14dd4ced77db..21e76bcd18b4 100644 --- a/lib/matplotlib/tests/test_colors.py +++ b/lib/matplotlib/tests/test_colors.py @@ -1307,6 +1307,51 @@ def test_to_rgba_array_alpha_array(): assert_array_equal(c[:, 3], alpha) +def test_to_rgba_array_accepts_color_alpha_tuple(): + assert_array_equal( + mcolors.to_rgba_array(('black', 0.9)), + [[0, 0, 0, 0.9]]) + + +def test_to_rgba_array_explicit_alpha_overrides_tuple_alpha(): + assert_array_equal( + mcolors.to_rgba_array(('black', 0.9), alpha=0.5), + [[0, 0, 0, 0.5]]) + + +def test_to_rgba_array_accepts_color_alpha_tuple_with_multiple_colors(): + color_array = np.array([[1., 1., 1., 1.], [0., 0., 1., 0.]]) + assert_array_equal( + mcolors.to_rgba_array((color_array, 0.2)), + [[1., 1., 1., 0.2], [0., 0., 1., 0.2]]) + + color_sequence = [[1., 1., 1., 1.], [0., 0., 1., 0.]] + assert_array_equal( + mcolors.to_rgba_array((color_sequence, 0.4)), + [[1., 1., 1., 0.4], [0., 0., 1., 0.4]]) + + +def test_to_rgba_array_error_with_color_invalid_alpha_tuple(): + with pytest.raises(ValueError, match="'alpha' must be between 0 and 1,"): + mcolors.to_rgba_array(('black', 2.0)) + + +@pytest.mark.parametrize('rgba_alpha', + [('white', 0.5), ('#ffffff', 0.5), ('#ffffff00', 0.5), + ((1.0, 1.0, 1.0, 1.0), 0.5)]) +def test_to_rgba_accepts_color_alpha_tuple(rgba_alpha): + assert mcolors.to_rgba(rgba_alpha) == (1, 1, 1, 0.5) + + +def test_to_rgba_explicit_alpha_overrides_tuple_alpha(): + assert mcolors.to_rgba(('red', 0.1), alpha=0.9) == (1, 0, 0, 0.9) + + +def test_to_rgba_error_with_color_invalid_alpha_tuple(): + with pytest.raises(ValueError, match="'alpha' must be between 0 and 1"): + mcolors.to_rgba(('blue', 2.0)) + + def test_failed_conversions(): with pytest.raises(ValueError): mcolors.to_rgba('5')
Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.
Alternative Proxies: