Skip to content

ENH: Add option to define a color as color=(some_color, some_alpha) #24691

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 1 commit into from
Mar 19, 2023
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
21 changes: 21 additions & 0 deletions doc/users/next_whats_new/new_color_spec_tuple.rst
Original file line number Diff line number Diff line change
@@ -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)*.
53 changes: 53 additions & 0 deletions galleries/examples/color/set_alpha.py
Original file line number Diff line number Diff line change
@@ -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`
3 changes: 3 additions & 0 deletions galleries/users_explain/colors/colors.py
Original file line number Diff line number Diff line change
Expand Up @@ -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/

Expand Down
19 changes: 17 additions & 2 deletions lib/matplotlib/colors.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.)
Expand Down Expand Up @@ -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.)
Expand Down Expand Up @@ -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.")

Expand Down
45 changes: 45 additions & 0 deletions lib/matplotlib/tests/test_colors.py
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down