Skip to content

Commit

Permalink
Merge pull request matplotlib#24691 from j1642/patch_alpha
Browse files Browse the repository at this point in the history
ENH: Add option to define a color as color=(some_color, some_alpha)
  • Loading branch information
story645 authored Mar 19, 2023
2 parents 78bf53c + 08fec0d commit 17fd9de
Show file tree
Hide file tree
Showing 5 changed files with 139 additions and 2 deletions.
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

0 comments on commit 17fd9de

Please sign in to comment.