diff --git a/doc/users/next_whats_new/shorthand_hex_colors.rst b/doc/users/next_whats_new/shorthand_hex_colors.rst new file mode 100644 index 000000000000..a4b0c14c2055 --- /dev/null +++ b/doc/users/next_whats_new/shorthand_hex_colors.rst @@ -0,0 +1,6 @@ +3-digit and 4-digit hex colors +------------------------------ + +Colors can now be specified using 3-digit or 4-digit hex colors, shorthand for +the colors obtained by duplicating each character, e.g. ``#123`` is equivalent to +``#112233`` and ``#123a`` is equivalent to ``#112233aa``. \ No newline at end of file diff --git a/lib/matplotlib/colors.py b/lib/matplotlib/colors.py index c71e9ef98396..1aa4ca9cd40d 100644 --- a/lib/matplotlib/colors.py +++ b/lib/matplotlib/colors.py @@ -40,6 +40,10 @@ interval ``[0, 1]`` (e.g., ``(0.1, 0.2, 0.5)`` or ``(0.1, 0.2, 0.5, 0.3)``); * a hex RGB or RGBA string (e.g., ``'#0f0f0f'`` or ``'#0f0f0f80'``; case-insensitive); +* a shorthand hex RGB or RGBA string, equivalent to the hex RGB or RGBA + string obtained by duplicating each character, (e.g., ``'#abc'``, equivalent + to ``'#aabbcc'``, or ``'#abcd'``, equivalent to ``'#aabbccdd'``; + case-insensitive); * a string representation of a float value in ``[0, 1]`` inclusive for gray level (e.g., ``'0.5'``); * one of ``{'b', 'g', 'r', 'c', 'm', 'y', 'k', 'w'}``, they are the single @@ -215,13 +219,19 @@ def _to_rgba_no_colorcycle(c, alpha=None): "%(since)s and will be removed %(removal)s; please " "use lowercase instead.") if isinstance(c, str): - # hex color with no alpha. + # hex color in #rrggbb format. match = re.match(r"\A#[a-fA-F0-9]{6}\Z", c) if match: return (tuple(int(n, 16) / 255 for n in [c[1:3], c[3:5], c[5:7]]) + (alpha if alpha is not None else 1.,)) - # hex color with alpha. + # hex color in #rgb format, shorthand for #rrggbb. + match = re.match(r"\A#[a-fA-F0-9]{3}\Z", c) + if match: + return (tuple(int(n, 16) / 255 + for n in [c[1]*2, c[2]*2, c[3]*2]) + + (alpha if alpha is not None else 1.,)) + # hex color with alpha in #rrggbbaa format. match = re.match(r"\A#[a-fA-F0-9]{8}\Z", c) if match: color = [int(n, 16) / 255 @@ -229,6 +239,14 @@ def _to_rgba_no_colorcycle(c, alpha=None): if alpha is not None: color[-1] = alpha return tuple(color) + # hex color with alpha in #rgba format, shorthand for #rrggbbaa. + match = re.match(r"\A#[a-fA-F0-9]{4}\Z", c) + if match: + color = [int(n, 16) / 255 + for n in [c[1]*2, c[2]*2, c[3]*2, c[4]*2]] + if alpha is not None: + color[-1] = alpha + return tuple(color) # string gray. try: c = float(c) diff --git a/lib/matplotlib/tests/test_colors.py b/lib/matplotlib/tests/test_colors.py index 9d3364bbb821..96e34edfbd74 100644 --- a/lib/matplotlib/tests/test_colors.py +++ b/lib/matplotlib/tests/test_colors.py @@ -910,3 +910,8 @@ def __add__(self, other): def test_same_color(): assert mcolors.same_color('k', (0, 0, 0)) assert not mcolors.same_color('w', (1, 1, 0)) + + +def test_hex_shorthand_notation(): + assert mcolors.same_color("#123", "#112233") + assert mcolors.same_color("#123a", "#112233aa") diff --git a/tutorials/colors/colors.py b/tutorials/colors/colors.py index ef77240a18fb..d2b462f25f60 100644 --- a/tutorials/colors/colors.py +++ b/tutorials/colors/colors.py @@ -9,6 +9,10 @@ interval ``[0, 1]`` (e.g., ``(0.1, 0.2, 0.5)`` or ``(0.1, 0.2, 0.5, 0.3)``); * a hex RGB or RGBA string (e.g., ``'#0f0f0f'`` or ``'#0f0f0f80'``; case-insensitive); +* a shorthand hex RGB or RGBA string, equivalent to the hex RGB or RGBA + string obtained by duplicating each character, (e.g., ``'#abc'``, equivalent + to ``'#aabbcc'``, or ``'#abcd'``, equivalent to ``'#aabbccdd'``; + case-insensitive); * a string representation of a float value in ``[0, 1]`` inclusive for gray level (e.g., ``'0.5'``); * one of ``{'b', 'g', 'r', 'c', 'm', 'y', 'k', 'w'}``, they are the single