From f6f908e82f5fe231af6de11b1d21e22035a86a58 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Fri, 6 May 2016 13:24:33 -0700 Subject: [PATCH 01/11] New color conversion machinery. --- lib/matplotlib/colors.py | 377 +++++++++++++--------------- lib/matplotlib/tests/test_colors.py | 7 + 2 files changed, 176 insertions(+), 208 deletions(-) diff --git a/lib/matplotlib/colors.py b/lib/matplotlib/colors.py index 5c5fa7fd83c8..e36adcdb1cab 100644 --- a/lib/matplotlib/colors.py +++ b/lib/matplotlib/colors.py @@ -15,9 +15,11 @@ :class:`ListedColormap`, which is used for generating a custom colormap from a list of color specifications. -The module also provides a single instance, *colorConverter*, of the -:class:`ColorConverter` class providing methods for converting single color -specifications or sequences of them to *RGB* or *RGBA*. +The module also provides functions for checking whether an object can be +interpreted as a color (:func:`is_color_like`), for converting such an object +to an RGBA tuple (:func:`to_rgba`) or to an HTML hex string (:func:`to_hex`), +and a sequence of colors to an `(n, 4)` RGBA array (:func:`to_rgba_array`). +Caching is used for efficiency. Commands which take color arguments can use several formats to specify the colors. For the basic built-in colors, you can use a single letter @@ -44,7 +46,7 @@ For a greater range of colors, you have two options. You can specify the color using an html hex string, as in:: - color = '#eeefff' + color = '#eeefff' or you can pass an *R* , *G* , *B* tuple, where each of *R* , *G* , *B* are in the range [0,1]. @@ -52,6 +54,7 @@ Finally, legal html names for colors, like 'red', 'burlywood' and 'chartreuse' are supported. """ + from __future__ import (absolute_import, division, print_function, unicode_literals) import re @@ -64,53 +67,172 @@ import matplotlib.cbook as cbook from ._color_data import XKCD_COLORS, CSS4_COLORS -# for back copatibility -cnames = CSS4_COLORS -COLOR_NAMES = {'xkcd': XKCD_COLORS, - 'css4': CSS4_COLORS} +_colors_full_map = { + 'b': (0, 0, 1), + 'g': (0, 0.5, 0), + 'r': (1, 0, 0), + 'c': (0, 0.75, 0.75), + 'm': (0.75, 0, 0.75), + 'y': (0.75, 0.75, 0), + 'k': (0, 0, 0), + 'w': (1, 1, 1)} +_colors_full_map.update(XKCD_COLORS) +_colors_full_map.update(CSS4_COLORS) -def is_color_like(c): - 'Return *True* if *c* can be converted to *RGB*' - - # Special-case the N-th color cycle syntax, because its parsing - # needs to be deferred. We may be reading a value from rcParams - # here before the color_cycle rcParam has been parsed. - if isinstance(c, bytes): - match = re.match(b'^C[0-9]$', c) - if match is not None: - return True - elif isinstance(c, six.text_type): - match = re.match('^C[0-9]$', c) - if match is not None: - return True +_colors_cache = {} - try: - colorConverter.to_rgb(c) + +def _is_nth_color(c): + """Return whether `c` can be interpreted as an item in the color cycle. + """ + return isinstance(c, six.string_types) and re.match(r"\AC[0-9]\Z", c) + + +def is_color_like(c): + """Return whether `c` can be interpreted as an RGB(A) color. + """ + # Special-case nth color syntax because it cannot be parsed during + # setup. + if _is_nth_color(c): return True + try: + to_rgba(c) except ValueError: return False + else: + return True -def rgb2hex(rgb): - 'Given an rgb or rgba sequence of 0-1 floats, return the hex string' - a = '#%02x%02x%02x' % tuple([int(np.round(val * 255)) for val in rgb[:3]]) - return a +def to_rgba(c, alpha=None): + """Convert `c` to an RGBA color. + + `alpha` provides a default alpha value. + """ + # Special-case nth color syntax because it should not be cached. + if _is_nth_color(c): + from matplotlib import rcParams + from matplotlib.rcsetup import cycler + prop_cycler = rcParams['axes.prop_cycle'] + if prop_cycler is None and 'axes.color_cycle' in rcParams: + clist = rcParams['axes.color_cycle'] + prop_cycler = cycler('color', clist) + colors = prop_cycler._transpose()['color'] + c = colors[int(c[1]) % len(colors)] + try: + rgba = _colors_cache[c, alpha] + except (KeyError, TypeError): # Not in cache, or unhashable. + rgba = _to_rgba_no_colorcycle(c, alpha) + try: + _colors_cache[c, alpha] = rgba + except TypeError: + pass + return rgba + + +def _to_rgba_no_colorcycle(c, alpha=None): + """Convert `c` to an RGBA color, with no support for color-cycle syntax. + + `alpha` provides a default alpha value. + """ + orig_c = c + if isinstance(c, six.string_types) and c.lower() == "none": + return (0., 0., 0., 0.) + if isinstance(c, six.string_types): + # Named color. + try: + c = _colors_full_map[c.lower()] + except KeyError: + pass + if isinstance(c, six.string_types): + # hex color with no alpha. + 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.,)) + if isinstance(c, six.string_types): + # hex color with alpha. + match = re.match(r"\A#[a-fA-F0-9]{8}\Z", c) + if match: + color = [int(n, 16) / 255 + for n in [c[1:3], c[3:5], c[5:7], c[7:9]]] + if alpha is not None: + color[-1] = alpha + return tuple(color) + if isinstance(c, six.string_types): + # string gray. + try: + return (float(c),) * 3 + (1.,) + except ValueError: + pass + raise ValueError("Invalid RGBA argument: {!r}".format(orig_c)) + # tuple color. + try: + c = np.array(c, float) + except TypeError: + raise ValueError("Invalid RGBA argument: {!r}".format(orig_c)) + if c.ndim != 1: + raise ValueError("Invalid RGBA argument: {!r}".format(orig_c)) + if len(c) == 3: + c = np.append(c, alpha if alpha is not None else 1.) + if len(c) == 4: + if np.any((c < 0) | (c > 1)): + raise ValueError("RGBA values should be within 0-1 range") + if alpha is not None: + c[-1] = alpha + return tuple(c) + else: + raise ValueError("RGBA sequence should have length 3 or 4") + + +def to_rgba_array(c, alpha=None): + """Convert `c` to a (n, 4) array of RGBA colors. + + `alpha` provides a default alpha value. If `c` is "none" + (case-insensitive) or an empty list, an empty array is returned. + """ + # Single value? + if isinstance(c, six.string_types) and c.lower() == "none": + return np.zeros((0, 4), float) + try: + return np.array([to_rgba(c, alpha)], float) + except (ValueError, TypeError): + pass + result = np.empty((len(c), 4), float) + for i, cc in enumerate(c): + result[i] = to_rgba(cc, alpha) + return result + + +def to_hex(c, alpha=None): + """Convert `c` to a hex color. + + `alpha` provides a default alpha value. + """ + return "#" + "".join(format(int(np.round(val * 255)), "02x") + for val in to_rgba(c, alpha=alpha)) + + +### Backwards-compatible color-conversion API +cnames = CSS4_COLORS +COLOR_NAMES = {'xkcd': XKCD_COLORS, 'css4': CSS4_COLORS} hexColorPattern = re.compile("\A#[a-fA-F0-9]{6}\Z") -def hex2color(s): +def rgb2hex(c): + 'Given an rgb or rgba sequence of 0-1 floats, return the hex string' + return to_hex(c)[:7] # Drop alpha. + + +def hex2color(c): """ Take a hex string *s* and return the corresponding rgb 3-tuple Example: #efefef -> (0.93725, 0.93725, 0.93725) """ - if not isinstance(s, six.string_types): - raise TypeError('hex2color requires a string argument') - if hexColorPattern.match(s) is None: - raise ValueError('invalid hex color string "%s"' % s) - return tuple([int(n, 16) / 255.0 for n in (s[1:3], s[3:5], s[5:7])]) + return ColorConverter.to_rgb(c) class ColorConverter(object): @@ -123,47 +245,12 @@ class ColorConverter(object): Ordinarily only the single instance instantiated in this module, *colorConverter*, is needed. """ - colors = { - 'b': (0, 0, 1), - 'g': (0, 0.5, 0), - 'r': (1, 0, 0), - 'c': (0, 0.75, 0.75), - 'm': (0.75, 0, 0.75), - 'y': (0.75, 0.75, 0), - 'k': (0, 0, 0), - 'w': (1, 1, 1)} - - _prop_cycler = None - - cache = {} - CN_LOOKUPS = [COLOR_NAMES[k] for k in ['css4', 'xkcd']] - - @classmethod - def _get_nth_color(cls, val): - """ - Get the Nth color in the current color cycle. If N is greater - than the number of colors in the cycle, it is wrapped around. - """ - from matplotlib.rcsetup import cycler - from matplotlib import rcParams - - prop_cycler = rcParams['axes.prop_cycle'] - if prop_cycler is None and 'axes.color_cycle' in rcParams: - clist = rcParams['axes.color_cycle'] - prop_cycler = cycler('color', clist) - - colors = prop_cycler._transpose()['color'] - return colors[val % len(colors)] - @classmethod - def _parse_nth_color(cls, val): - match = re.match('^C[0-9]$', val) - if match is not None: - return cls._get_nth_color(int(val[1])) + colors = _colors_full_map + cache = _colors_cache - raise ValueError("Not a color cycle color") - - def to_rgb(self, arg): + @staticmethod + def to_rgb(arg): """ Returns an *RGB* tuple of three floats from 0-1. @@ -178,70 +265,10 @@ def to_rgb(self, arg): if *arg* is *RGBA*, the *A* will simply be discarded. """ - # Gray must be a string to distinguish 3-4 grays from RGB or RGBA. - - try: - return self.cache[arg] - except KeyError: - pass - except TypeError: # could be unhashable rgb seq - arg = tuple(arg) - try: - return self.cache[arg] - except KeyError: - pass - except TypeError: - raise ValueError( - 'to_rgb: arg "%s" is unhashable even inside a tuple' - % (str(arg),)) - try: - if cbook.is_string_like(arg): - argl = arg.lower() - color = self.colors.get(argl, None) - if color is None: - try: - argl = self._parse_nth_color(arg) - # in this case we do not want to cache in case - # the rcparam changes, recurse with the actual color - # value - return self.to_rgb(argl) - except ValueError: - pass - for cmapping in self.CN_LOOKUPS: - str1 = cmapping.get(argl, argl) - if str1 != argl: - break - if str1.startswith('#'): - color = hex2color(str1) - else: - fl = float(argl) - if fl < 0 or fl > 1: - raise ValueError( - 'gray (string) must be in range 0-1') - color = (fl,) * 3 - elif cbook.iterable(arg): - if len(arg) > 4 or len(arg) < 3: - raise ValueError( - 'sequence length is %d; must be 3 or 4' % len(arg)) - color = tuple(arg[:3]) - if [x for x in color if (float(x) < 0) or (x > 1)]: - # This will raise TypeError if x is not a number. - raise ValueError( - 'number in rbg sequence outside 0-1 range') - else: - raise ValueError( - 'cannot convert argument to rgb sequence') - self.cache[arg] = color - - except (KeyError, ValueError, TypeError) as exc: - raise ValueError( - 'to_rgb: Invalid rgb arg "%s"\n%s' % (str(arg), exc)) - # Error messages could be improved by handling TypeError - # separately; but this should be rare and not too hard - # for the user to figure out as-is. - return color + return to_rgba(arg)[:3] - def to_rgba(self, arg, alpha=None): + @staticmethod + def to_rgba(arg, alpha=None): """ Returns an *RGBA* tuple of four floats from 0-1. @@ -251,42 +278,10 @@ def to_rgba(self, arg, alpha=None): If *arg* is an *RGBA* sequence and *alpha* is not *None*, *alpha* will replace the original *A*. """ - try: - if arg.lower() == 'none': - return (0.0, 0.0, 0.0, 0.0) - except AttributeError: - pass - - if alpha is not None and (alpha < 0.0 or alpha > 1.0): - raise ValueError("alpha must be in range 0-1") + return to_rgba(arg, alpha) - try: - if not cbook.is_string_like(arg) and cbook.iterable(arg): - if len(arg) == 4: - if any(float(x) < 0 or x > 1 for x in arg): - raise ValueError( - 'number in rbga sequence outside 0-1 range') - if alpha is None: - return tuple(arg) - return arg[0], arg[1], arg[2], alpha - if len(arg) == 3: - r, g, b = arg - if any(float(x) < 0 or x > 1 for x in arg): - raise ValueError( - 'number in rbg sequence outside 0-1 range') - else: - raise ValueError( - 'length of rgba sequence should be either 3 or 4') - else: - r, g, b = self.to_rgb(arg) - if alpha is None: - alpha = 1.0 - return r, g, b, alpha - except (TypeError, ValueError) as exc: - raise ValueError( - 'to_rgba: Invalid rgba arg "%s"\n%s' % (str(arg), exc)) - - def to_rgba_array(self, c, alpha=None): + @staticmethod + def to_rgba_array(arg, alpha=None): """ Returns a numpy array of *RGBA* tuples. @@ -295,47 +290,13 @@ def to_rgba_array(self, c, alpha=None): Special case to handle "no color": if *c* is "none" (case-insensitive), then an empty array will be returned. Same for an empty list. """ - try: - nc = len(c) - except TypeError: - raise ValueError( - "Cannot convert argument type %s to rgba array" % type(c)) - try: - if nc == 0 or c.lower() == 'none': - return np.zeros((0, 4), dtype=np.float) - except AttributeError: - pass - try: - # Single value? Put it in an array with a single row. - return np.array([self.to_rgba(c, alpha)], dtype=np.float) - except ValueError: - if isinstance(c, np.ndarray): - if c.ndim != 2 and c.dtype.kind not in 'SU': - raise ValueError("Color array must be two-dimensional") - if (c.ndim == 2 and c.shape[1] == 4 and c.dtype.kind == 'f'): - if (c.ravel() > 1).any() or (c.ravel() < 0).any(): - raise ValueError( - "number in rgba sequence is outside 0-1 range") - result = np.asarray(c, np.float) - if alpha is not None: - if alpha > 1 or alpha < 0: - raise ValueError("alpha must be in 0-1 range") - result[:, 3] = alpha - return result - # This alpha operation above is new, and depends - # on higher levels to refrain from setting alpha - # to values other than None unless there is - # intent to override any existing alpha values. - - # It must be some other sequence of color specs. - result = np.zeros((nc, 4), dtype=np.float) - for i, cc in enumerate(c): - result[i] = self.to_rgba(cc, alpha) - return result + return to_rgba_array(arg, alpha) colorConverter = ColorConverter() +### End of backwards-compatible color-conversion API + def makeMappingArray(N, data, gamma=1.0): """Create an *N* -element 1-d lookup table diff --git a/lib/matplotlib/tests/test_colors.py b/lib/matplotlib/tests/test_colors.py index 193ce0094c15..2bd83b58e220 100644 --- a/lib/matplotlib/tests/test_colors.py +++ b/lib/matplotlib/tests/test_colors.py @@ -628,6 +628,13 @@ def test_cn(): assert red == '#ff0000' +def test_conversions(): + # to_rgba_array("none") returns a (0, 4) array. + assert_array_equal(mcolors.to_rgba_array("none"), np.zeros((0, 4))) + # alpha is properly set. + assert_equal(mcolors.to_rgba((1, 1, 1), .5), (1, 1, 1, .5)) + + if __name__ == '__main__': import nose nose.runmodule(argv=['-s', '--with-doctest'], exit=False) From 75b0d131783c3d774ddce63c3c35d76264d86d56 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Sun, 8 May 2016 00:37:10 -0700 Subject: [PATCH 02/11] Update named_colors example. --- examples/color/named_colors.py | 41 +++++++++++----------------------- 1 file changed, 13 insertions(+), 28 deletions(-) diff --git a/examples/color/named_colors.py b/examples/color/named_colors.py index 82ae356031d0..65d8b541205e 100644 --- a/examples/color/named_colors.py +++ b/examples/color/named_colors.py @@ -11,36 +11,19 @@ import numpy as np import matplotlib.pyplot as plt -from matplotlib import colors +from matplotlib import colors as mcolors -colors_ = list(six.iteritems(colors.cnames)) - -# Add the single letter colors. -for name, rgb in six.iteritems(colors.ColorConverter.colors): - hex_ = colors.rgb2hex(rgb) - colors_.append((name, hex_)) - -# Transform to hex color values. -hex_ = [color[1] for color in colors_] -# Get the rgb equivalent. -rgb = [colors.hex2color(color) for color in hex_] -# Get the hsv equivalent. -hsv = [colors.rgb_to_hsv(color) for color in rgb] - -# Split the hsv values to sort. -hue = [color[0] for color in hsv] -sat = [color[1] for color in hsv] -val = [color[2] for color in hsv] - -# Get the color names by themselves. -names = [color[0] for color in colors_] +colors = mcolors.CSS4_COLORS # Sort by hue, saturation, value and name. -ind = np.lexsort((names, val, sat, hue)) -sorted_colors = [colors_[i] for i in ind] +by_hsv = sorted((tuple(mcolors.rgb_to_hsv(mcolors.to_rgba(color)[:3])), name) + for name, color in colors.items()) + +# Get the sorted color names. +sorted_names = [name for hsv, name in by_hsv] -n = len(sorted_colors) +n = len(sorted_names) ncols = 4 nrows = int(np.ceil(1. * n / ncols)) @@ -53,7 +36,7 @@ # col width w = X / ncols -for i, (name, color) in enumerate(sorted_colors): +for i, name in enumerate(sorted_names): col = i % ncols row = int(i / ncols) y = Y - (row * h) - h @@ -68,8 +51,10 @@ # Add extra black line a little bit thicker to make # clear colors more visible. - ax.hlines(y, xi_line, xf_line, color='black', linewidth=(h * 0.7)) - ax.hlines(y + h * 0.1, xi_line, xf_line, color=color, linewidth=(h * 0.6)) + ax.hlines( + y, xi_line, xf_line, color='black', linewidth=(h * 0.7)) + ax.hlines( + y + h * 0.1, xi_line, xf_line, color=colors[name], linewidth=(h * 0.6)) ax.set_xlim(0, X) ax.set_ylim(0, Y) From 72f34be30f74b7049e4bfc5680673cc15e028cf3 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Sun, 8 May 2016 00:48:49 -0700 Subject: [PATCH 03/11] New API for accessing the named colors mapping. For use e.g. by `seaborn.set(color_codes=True)`. --- lib/matplotlib/colors.py | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/lib/matplotlib/colors.py b/lib/matplotlib/colors.py index e36adcdb1cab..6f40a195bc97 100644 --- a/lib/matplotlib/colors.py +++ b/lib/matplotlib/colors.py @@ -68,6 +68,20 @@ from ._color_data import XKCD_COLORS, CSS4_COLORS +class _ColorMapping(dict): + def __init__(self, mapping, cache): + super(_ColorMapping, self).__init__(mapping) + self._cache = cache + + def __setitem__(self, key, value): + super(_ColorMapping, self).__setitem__(key, value) + self._cache.clear() + + def __delitem__(self, key, value): + super(_ColorMapping, self).__delitem__(key, value) + self._cache.clear() + + _colors_full_map = { 'b': (0, 0, 1), 'g': (0, 0.5, 0), @@ -79,9 +93,13 @@ 'w': (1, 1, 1)} _colors_full_map.update(XKCD_COLORS) _colors_full_map.update(CSS4_COLORS) +_colors_full_map = _ColorMapping(_colors_full_map, {}) -_colors_cache = {} +def get_named_colors_mapping(): + """Return the global mapping of names to named colors. + """ + return _colors_full_map def _is_nth_color(c): @@ -121,11 +139,11 @@ def to_rgba(c, alpha=None): colors = prop_cycler._transpose()['color'] c = colors[int(c[1]) % len(colors)] try: - rgba = _colors_cache[c, alpha] + rgba = _colors_full_map._cache[c, alpha] except (KeyError, TypeError): # Not in cache, or unhashable. rgba = _to_rgba_no_colorcycle(c, alpha) try: - _colors_cache[c, alpha] = rgba + _colors_full_map._cache[c, alpha] = rgba except TypeError: pass return rgba @@ -247,7 +265,7 @@ class ColorConverter(object): """ colors = _colors_full_map - cache = _colors_cache + cache = _colors_full_map._cache @staticmethod def to_rgb(arg): From 09a0f4c44c08166488194fa1776340e3d6e7ac17 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Sun, 8 May 2016 12:36:35 -0700 Subject: [PATCH 04/11] Faster conversion for RGBA arrays; more tests. --- lib/matplotlib/colors.py | 16 ++++++++++++++++ lib/matplotlib/tests/test_colors.py | 2 ++ 2 files changed, 18 insertions(+) diff --git a/lib/matplotlib/colors.py b/lib/matplotlib/colors.py index 6f40a195bc97..aea8a58bd0d2 100644 --- a/lib/matplotlib/colors.py +++ b/lib/matplotlib/colors.py @@ -218,6 +218,22 @@ def to_rgba_array(c, alpha=None): return np.array([to_rgba(c, alpha)], float) except (ValueError, TypeError): pass + # 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.) + if (isinstance(c, np.ndarray) and c.dtype.kind in "if" + and c.ndim == 2 and c.shape[1] in [3, 4]): + if c.shape[1] == 3: + result = np.column_stack([c, np.zeros(len(c))]) + result[:, -1] = alpha if alpha is not None else 1. + elif c.shape[1] == 4: + result = c.copy() + if alpha is not None: + result[:, -1] = alpha + if np.any((result < 0) | (result > 1)): + raise ValueError("RGBA values should be within 0-1 range") + return result + # Convert one at a time. result = np.empty((len(c), 4), float) for i, cc in enumerate(c): result[i] = to_rgba(cc, alpha) diff --git a/lib/matplotlib/tests/test_colors.py b/lib/matplotlib/tests/test_colors.py index 2bd83b58e220..737ef23053d9 100644 --- a/lib/matplotlib/tests/test_colors.py +++ b/lib/matplotlib/tests/test_colors.py @@ -633,6 +633,8 @@ def test_conversions(): assert_array_equal(mcolors.to_rgba_array("none"), np.zeros((0, 4))) # alpha is properly set. assert_equal(mcolors.to_rgba((1, 1, 1), .5), (1, 1, 1, .5)) + # builtin round differs between py2 and py3. + assert_equal(mcolors.to_hex((.7, .7, .7)), "#b2b2b2ff") if __name__ == '__main__': From 2fe2c266289e4edfa1c1311106ccd6a758354014 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Sun, 8 May 2016 14:07:02 -0700 Subject: [PATCH 05/11] Return builtin floats, not numpy floats. --- lib/matplotlib/colors.py | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/lib/matplotlib/colors.py b/lib/matplotlib/colors.py index aea8a58bd0d2..34f5ca81050e 100644 --- a/lib/matplotlib/colors.py +++ b/lib/matplotlib/colors.py @@ -126,7 +126,7 @@ def is_color_like(c): def to_rgba(c, alpha=None): """Convert `c` to an RGBA color. - `alpha` provides a default alpha value. + If `alpha` is not `None`, it forces the alpha value. """ # Special-case nth color syntax because it should not be cached. if _is_nth_color(c): @@ -152,7 +152,7 @@ def to_rgba(c, alpha=None): def _to_rgba_no_colorcycle(c, alpha=None): """Convert `c` to an RGBA color, with no support for color-cycle syntax. - `alpha` provides a default alpha value. + If `alpha` is not `None`, it forces the alpha value. """ orig_c = c if isinstance(c, six.string_types) and c.lower() == "none": @@ -187,28 +187,27 @@ def _to_rgba_no_colorcycle(c, alpha=None): pass raise ValueError("Invalid RGBA argument: {!r}".format(orig_c)) # tuple color. + # Python 2.7 / numpy 1.6 apparently require this to return builtin floats, + # not numpy floats. try: - c = np.array(c, float) + c = tuple(map(float, c)) except TypeError: raise ValueError("Invalid RGBA argument: {!r}".format(orig_c)) - if c.ndim != 1: - raise ValueError("Invalid RGBA argument: {!r}".format(orig_c)) - if len(c) == 3: - c = np.append(c, alpha if alpha is not None else 1.) - if len(c) == 4: - if np.any((c < 0) | (c > 1)): - raise ValueError("RGBA values should be within 0-1 range") - if alpha is not None: - c[-1] = alpha - return tuple(c) - else: + if len(c) not in [3, 4]: raise ValueError("RGBA sequence should have length 3 or 4") + if len(c) == 3 and alpha is None: + alpha = 1 + if alpha is not None: + c = c[:3] + (alpha,) + if any(elem < 0 or elem > 1 for elem in c): + raise ValueError("RGBA values should be within 0-1 range") + return c def to_rgba_array(c, alpha=None): """Convert `c` to a (n, 4) array of RGBA colors. - `alpha` provides a default alpha value. If `c` is "none" + If `alpha` is not `None`, it forces the alpha value. If `c` is "none" (case-insensitive) or an empty list, an empty array is returned. """ # Single value? @@ -243,7 +242,7 @@ def to_rgba_array(c, alpha=None): def to_hex(c, alpha=None): """Convert `c` to a hex color. - `alpha` provides a default alpha value. + If `alpha` is not `None`, it forces the alpha value. """ return "#" + "".join(format(int(np.round(val * 255)), "02x") for val in to_rgba(c, alpha=alpha)) From 5a4432c362a658bb0584dfd99332fe628e090c05 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Sun, 8 May 2016 15:33:16 -0700 Subject: [PATCH 06/11] Remove use of colorConverter. Except in Qt options editor, which is under a separate PR. This also makes it possible to call plt.plot([1, 2, 3], "#ff000040") (setting alpha at the same time). --- examples/api/collections_demo.py | 5 +-- examples/event_handling/lasso_demo.py | 7 ++-- examples/mplot3d/polys3d_demo.py | 4 +- examples/pylab_examples/colours.py | 4 +- examples/pylab_examples/demo_ribbon_box.py | 2 +- examples/pylab_examples/line_collection.py | 4 +- examples/widgets/menu.py | 4 +- lib/matplotlib/axes/_axes.py | 8 ++-- lib/matplotlib/axes/_base.py | 8 ++-- lib/matplotlib/backend_bases.py | 4 +- lib/matplotlib/backends/backend_agg.py | 5 +-- lib/matplotlib/backends/backend_gtk.py | 16 +++----- lib/matplotlib/backends/backend_gtk3.py | 6 +-- lib/matplotlib/collections.py | 10 ++--- lib/matplotlib/finance.py | 47 +++++++--------------- lib/matplotlib/image.py | 2 +- lib/matplotlib/legend_handler.py | 2 +- lib/matplotlib/lines.py | 15 ++----- lib/matplotlib/mathtext.py | 10 ++--- lib/matplotlib/patches.py | 7 ++-- lib/matplotlib/patheffects.py | 9 ++--- lib/matplotlib/tests/test_cbook.py | 2 +- lib/matplotlib/tests/test_colors.py | 28 ++++--------- lib/matplotlib/tests/test_rcparams.py | 28 +++++-------- lib/mpl_toolkits/mplot3d/art3d.py | 14 +++---- lib/mpl_toolkits/mplot3d/axes3d.py | 11 ++--- 26 files changed, 102 insertions(+), 160 deletions(-) diff --git a/examples/api/collections_demo.py b/examples/api/collections_demo.py index 6d58b180855a..1a704f79563d 100644 --- a/examples/api/collections_demo.py +++ b/examples/api/collections_demo.py @@ -17,8 +17,7 @@ ''' import matplotlib.pyplot as plt -from matplotlib import collections, transforms -from matplotlib.colors import colorConverter +from matplotlib import collections, colors, transforms import numpy as np nverts = 50 @@ -38,7 +37,7 @@ xyo = list(zip(xo, yo)) # Make a list of colors cycling through the default series. -colors = [colorConverter.to_rgba(c) +colors = [colors.to_rgba(c) for c in plt.rcParams['axes.prop_cycle'].by_key()['color']] fig, axes = plt.subplots(2, 2) diff --git a/examples/event_handling/lasso_demo.py b/examples/event_handling/lasso_demo.py index 75e44286c548..c704692e1277 100644 --- a/examples/event_handling/lasso_demo.py +++ b/examples/event_handling/lasso_demo.py @@ -7,9 +7,8 @@ usable as is). There will be some refinement of the API. """ from matplotlib.widgets import Lasso -from matplotlib.colors import colorConverter from matplotlib.collections import RegularPolyCollection -from matplotlib import path +from matplotlib import colors as mcolors, path import matplotlib.pyplot as plt from numpy import nonzero @@ -17,8 +16,8 @@ class Datum(object): - colorin = colorConverter.to_rgba('red') - colorout = colorConverter.to_rgba('blue') + colorin = mcolors.to_rgba("red") + colorout = mcolors.to_rgba("blue") def __init__(self, x, y, include=False): self.x = x diff --git a/examples/mplot3d/polys3d_demo.py b/examples/mplot3d/polys3d_demo.py index 304c310e682a..7b94a19d04cb 100644 --- a/examples/mplot3d/polys3d_demo.py +++ b/examples/mplot3d/polys3d_demo.py @@ -5,8 +5,8 @@ from mpl_toolkits.mplot3d import Axes3D from matplotlib.collections import PolyCollection -from matplotlib.colors import colorConverter import matplotlib.pyplot as plt +from matplotlib import colors as mcolors import numpy as np @@ -14,7 +14,7 @@ def cc(arg): ''' Shorthand to convert 'named' colors to rgba format at 60% opacity. ''' - return colorConverter.to_rgba(arg, alpha=0.6) + return mcolors.to_rgba(arg, alpha=0.6) def polygon_under_graph(xlist, ylist): diff --git a/examples/pylab_examples/colours.py b/examples/pylab_examples/colours.py index b34d6a6d97e6..adcbd4331317 100644 --- a/examples/pylab_examples/colours.py +++ b/examples/pylab_examples/colours.py @@ -3,12 +3,12 @@ Some simple functions to generate colours. """ import numpy as np -from matplotlib.colors import colorConverter +from matplotlib import colors as mcolors def pastel(colour, weight=2.4): """ Convert colour into a nice pastel shade""" - rgb = np.asarray(colorConverter.to_rgb(colour)) + rgb = np.asarray(mcolors.to_rgba(colour)[:3]) # scale colour maxc = max(rgb) if maxc < 1.0 and maxc > 0: diff --git a/examples/pylab_examples/demo_ribbon_box.py b/examples/pylab_examples/demo_ribbon_box.py index df39dede071a..59eeddaa9f48 100644 --- a/examples/pylab_examples/demo_ribbon_box.py +++ b/examples/pylab_examples/demo_ribbon_box.py @@ -18,7 +18,7 @@ class RibbonBox(object): nx = original_image.shape[1] def __init__(self, color): - rgb = matplotlib.colors.colorConverter.to_rgb(color) + rgb = matplotlib.colors.to_rgba(color)[:3] im = np.empty(self.original_image.shape, self.original_image.dtype) diff --git a/examples/pylab_examples/line_collection.py b/examples/pylab_examples/line_collection.py index aefa7d0fe389..22ccbc37c0bc 100644 --- a/examples/pylab_examples/line_collection.py +++ b/examples/pylab_examples/line_collection.py @@ -1,6 +1,6 @@ import matplotlib.pyplot as plt from matplotlib.collections import LineCollection -from matplotlib.colors import colorConverter +from matplotlib import colors as mcolors import numpy as np @@ -30,7 +30,7 @@ # where onoffseq is an even length tuple of on and off ink in points. # If linestyle is omitted, 'solid' is used # See matplotlib.collections.LineCollection for more information -colors = [colorConverter.to_rgba(c) +colors = [mcolors.to_rgba(c) for c in plt.rcParams['axes.prop_cycle'].by_key()['color']] line_segments = LineCollection(segs, linewidths=(0.5, 1, 1.5, 2), diff --git a/examples/widgets/menu.py b/examples/widgets/menu.py index d6c5be4dfb98..846dd5ce1917 100644 --- a/examples/widgets/menu.py +++ b/examples/widgets/menu.py @@ -17,8 +17,8 @@ def __init__(self, fontsize=14, labelcolor='black', bgcolor='yellow', self.bgcolor = bgcolor self.alpha = alpha - self.labelcolor_rgb = colors.colorConverter.to_rgb(labelcolor) - self.bgcolor_rgb = colors.colorConverter.to_rgb(bgcolor) + self.labelcolor_rgb = colors.to_rgba(labelcolor)[:3] + self.bgcolor_rgb = colors.to_rgba(bgcolor)[:3] class MenuItem(artist.Artist): diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 57c138f9e300..7ed60b66cdde 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -2066,7 +2066,7 @@ def make_iterable(x): if color is None: color = [None] * nbars else: - color = list(mcolors.colorConverter.to_rgba_array(color)) + color = list(mcolors.to_rgba_array(color)) if len(color) == 0: # until to_rgba_array is changed color = [[0, 0, 0, 0]] if len(color) < nbars: @@ -2075,7 +2075,7 @@ def make_iterable(x): if edgecolor is None: edgecolor = [None] * nbars else: - edgecolor = list(mcolors.colorConverter.to_rgba_array(edgecolor)) + edgecolor = list(mcolors.to_rgba_array(edgecolor)) if len(edgecolor) == 0: # until to_rgba_array is changed edgecolor = [[0, 0, 0, 0]] if len(edgecolor) < nbars: @@ -3844,7 +3844,7 @@ def scatter(self, x, y, s=None, c=None, marker=None, cmap=None, norm=None, co = kwargs.pop('color', None) if co is not None: try: - mcolors.colorConverter.to_rgba_array(co) + mcolors.to_rgba_array(co) except ValueError: raise ValueError("'color' kwarg must be an mpl color" " spec or sequence of color specs.\n" @@ -6045,7 +6045,7 @@ def _normalize_input(inp, ename='input'): if color is None: color = [self._get_lines.get_next_color() for i in xrange(nx)] else: - color = mcolors.colorConverter.to_rgba_array(color) + color = mcolors.to_rgba_array(color) if len(color) != nx: raise ValueError("color kwarg must have one color per dataset") diff --git a/lib/matplotlib/axes/_base.py b/lib/matplotlib/axes/_base.py index 74b84cc104b0..3aa9af474ed3 100644 --- a/lib/matplotlib/axes/_base.py +++ b/lib/matplotlib/axes/_base.py @@ -68,7 +68,7 @@ def _process_plot_format(fmt): # Is fmt just a colorspec? try: - color = mcolors.colorConverter.to_rgb(fmt) + color = mcolors.to_rgba(fmt) # We need to differentiate grayscale '1.0' from tri_down marker '1' try: @@ -112,14 +112,14 @@ def _process_plot_format(fmt): raise ValueError( 'Illegal format string "%s"; two marker symbols' % fmt) marker = c - elif c in mcolors.colorConverter.colors: + elif c in mcolors.get_named_colors_mapping(): if color is not None: raise ValueError( 'Illegal format string "%s"; two color symbols' % fmt) color = c elif c == 'C' and i < len(chars) - 1: color_cycle_number = int(chars[i + 1]) - color = mcolors.colorConverter._get_nth_color(color_cycle_number) + color = mcolors.to_rgba("C{}".format(color_cycle_number)) i += 1 else: raise ValueError( @@ -3687,7 +3687,7 @@ def set_cursor_props(self, *args): lw, c = args else: raise ValueError('args must be a (linewidth, color) tuple') - c = mcolors.colorConverter.to_rgba(c) + c = mcolors.to_rgba(c) self._cursorProps = lw, c def get_children(self): diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index fe7310155336..277a06b47a1b 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -1022,11 +1022,11 @@ def set_foreground(self, fg, isRGBA=False): if self._forced_alpha and isRGBA: self._rgb = fg[:3] + (self._alpha,) elif self._forced_alpha: - self._rgb = colors.colorConverter.to_rgba(fg, self._alpha) + self._rgb = colors.to_rgba(fg, self._alpha) elif isRGBA: self._rgb = fg else: - self._rgb = colors.colorConverter.to_rgba(fg) + self._rgb = colors.to_rgba(fg) def set_graylevel(self, frac): """ diff --git a/lib/matplotlib/backends/backend_agg.py b/lib/matplotlib/backends/backend_agg.py index ea4d78f2a1b3..aaa003804373 100644 --- a/lib/matplotlib/backends/backend_agg.py +++ b/lib/matplotlib/backends/backend_agg.py @@ -568,9 +568,8 @@ def print_jpg(self, filename_or_obj, *args, **kwargs): # The image is "pasted" onto a white background image to safely # handle any transparency image = Image.frombuffer('RGBA', size, buf, 'raw', 'RGBA', 0, 1) - color = mcolors.colorConverter.to_rgb( - rcParams.get('savefig.facecolor', 'white')) - color = tuple([int(x * 255.0) for x in color]) + rgba = mcolors.to_rgba(rcParams.get('savefig.facecolor', 'white')) + color = tuple([int(x * 255.0) for x in rgba[:3]]) background = Image.new('RGB', size, color) background.paste(image, image) options = restrict_dict(kwargs, ['quality', 'optimize', diff --git a/lib/matplotlib/backends/backend_gtk.py b/lib/matplotlib/backends/backend_gtk.py index 7ebca484d7b2..de5fcce665bf 100644 --- a/lib/matplotlib/backends/backend_gtk.py +++ b/lib/matplotlib/backends/backend_gtk.py @@ -35,15 +35,11 @@ def fn_name(): return sys._getframe(1).f_code.co_name from matplotlib.backends.backend_gdk import RendererGDK, FigureCanvasGDK from matplotlib.cbook import is_string_like, is_writable_file_like -from matplotlib.colors import colorConverter from matplotlib.figure import Figure from matplotlib.widgets import SubplotTool -from matplotlib import lines -from matplotlib import markers -from matplotlib import cbook -from matplotlib import verbose -from matplotlib import rcParams +from matplotlib import ( + cbook, colors as mcolors, lines, markers, rcParams, verbose) backend_version = "%d.%d.%d" % gtk.pygtk_version @@ -1003,13 +999,13 @@ def on_combobox_lineprops_changed(self, item): if marker is None: marker = 'None' self.cbox_markers.set_active(self.markerd[marker]) - r,g,b = colorConverter.to_rgb(line.get_color()) - color = gtk.gdk.Color(*[int(val*65535) for val in (r,g,b)]) + rgba = mcolors.to_rgba(line.get_color()) + color = gtk.gdk.Color(*[int(val*65535) for val in rgba[:3]]) button = self.wtree.get_widget('colorbutton_linestyle') button.set_color(color) - r,g,b = colorConverter.to_rgb(line.get_markerfacecolor()) - color = gtk.gdk.Color(*[int(val*65535) for val in (r,g,b)]) + rgba = mcolors.to_rgba(line.get_markerfacecolor()) + color = gtk.gdk.Color(*[int(val*65535) for val in rgba[:3]]) button = self.wtree.get_widget('colorbutton_markerface') button.set_color(color) self._updateson = True diff --git a/lib/matplotlib/backends/backend_gtk3.py b/lib/matplotlib/backends/backend_gtk3.py index d8f2910c7f35..4f2ef04c5873 100644 --- a/lib/matplotlib/backends/backend_gtk3.py +++ b/lib/matplotlib/backends/backend_gtk3.py @@ -36,14 +36,10 @@ def fn_name(): return sys._getframe(1).f_code.co_name from matplotlib import backend_tools from matplotlib.cbook import is_string_like, is_writable_file_like -from matplotlib.colors import colorConverter from matplotlib.figure import Figure from matplotlib.widgets import SubplotTool -from matplotlib import lines -from matplotlib import cbook -from matplotlib import verbose -from matplotlib import rcParams +from matplotlib import cbook, colors as mcolors, lines, verbose, rcParams backend_version = "%s.%s.%s" % (Gtk.get_major_version(), Gtk.get_micro_version(), Gtk.get_minor_version()) diff --git a/lib/matplotlib/collections.py b/lib/matplotlib/collections.py index 5576def9d440..60fed3e41403 100644 --- a/lib/matplotlib/collections.py +++ b/lib/matplotlib/collections.py @@ -615,7 +615,7 @@ def set_facecolor(self, c): if c is None: c = mpl.rcParams['patch.facecolor'] self._facecolors_original = c - self._facecolors = mcolors.colorConverter.to_rgba_array(c, self._alpha) + self._facecolors = mcolors.to_rgba_array(c, self._alpha) self.stale = True def set_facecolors(self, c): @@ -663,7 +663,7 @@ def set_edgecolor(self, c): if c is None: c = mpl.rcParams['patch.edgecolor'] self._edgecolors_original = c - self._edgecolors = mcolors.colorConverter.to_rgba_array(c, self._alpha) + self._edgecolors = mcolors.to_rgba_array(c, self._alpha) self.stale = True def set_edgecolors(self, c): @@ -684,14 +684,14 @@ def set_alpha(self, alpha): raise TypeError('alpha must be a float or None') artist.Artist.set_alpha(self, alpha) try: - self._facecolors = mcolors.colorConverter.to_rgba_array( + self._facecolors = mcolors.to_rgba_array( self._facecolors_original, self._alpha) except (AttributeError, TypeError, IndexError): pass try: if (not isinstance(self._edgecolors_original, six.string_types) or self._edgecolors_original != str('face')): - self._edgecolors = mcolors.colorConverter.to_rgba_array( + self._edgecolors = mcolors.to_rgba_array( self._edgecolors_original, self._alpha) except (AttributeError, TypeError, IndexError): pass @@ -1137,7 +1137,7 @@ def __init__(self, segments, # Can be None. if antialiaseds is None: antialiaseds = (mpl.rcParams['lines.antialiased'],) - colors = mcolors.colorConverter.to_rgba_array(colors) + colors = mcolors.to_rgba_array(colors) Collection.__init__( self, diff --git a/lib/matplotlib/finance.py b/lib/matplotlib/finance.py index 6b5b1b3e510d..cbdb592c8b3e 100644 --- a/lib/matplotlib/finance.py +++ b/lib/matplotlib/finance.py @@ -21,11 +21,10 @@ import numpy as np -from matplotlib import verbose, get_cachedir +from matplotlib import colors as mcolors, verbose, get_cachedir from matplotlib.dates import date2num from matplotlib.cbook import iterable, mkdirs from matplotlib.collections import LineCollection, PolyCollection -from matplotlib.colors import colorConverter from matplotlib.lines import Line2D, TICKLEFT, TICKRIGHT from matplotlib.patches import Rectangle from matplotlib.transforms import Affine2D @@ -969,13 +968,9 @@ def plot_day_summary2_ohlc(ax, opens, highs, lows, closes, ticksize=4, tickTransform = Affine2D().scale(scale, 0.0) - r, g, b = colorConverter.to_rgb(colorup) - colorup = r, g, b, 1 - r, g, b = colorConverter.to_rgb(colordown) - colordown = r, g, b, 1 - colord = {True: colorup, - False: colordown, - } + colorup = mcolors.to_rgba(colorup) + colordown = mcolors.to_rgba(colordown) + colord = {True: colorup, False: colordown} colors = [colord[open < close] for open, close in zip(opens, closes) if open != -1 and close != -1] @@ -1113,13 +1108,9 @@ def candlestick2_ohlc(ax, opens, highs, lows, closes, width=4, for i, low, high in zip(xrange(len(lows)), lows, highs) if low != -1] - r, g, b = colorConverter.to_rgb(colorup) - colorup = r, g, b, alpha - r, g, b = colorConverter.to_rgb(colordown) - colordown = r, g, b, alpha - colord = {True: colorup, - False: colordown, - } + colorup = mcolors.to_rgba(colorup, alpha) + colordown = mcolors.to_rgba(colordown, alpha) + colord = {True: colorup, False: colordown} colors = [colord[open < close] for open, close in zip(opens, closes) if open != -1 and close != -1] @@ -1186,13 +1177,9 @@ def volume_overlay(ax, opens, closes, volumes, """ - r, g, b = colorConverter.to_rgb(colorup) - colorup = r, g, b, alpha - r, g, b = colorConverter.to_rgb(colordown) - colordown = r, g, b, alpha - colord = {True: colorup, - False: colordown, - } + colorup = mcolors.to_rgba(colorup, alpha) + colordown = mcolors.to_rgba(colordown, alpha) + colord = {True: colorup, False: colordown} colors = [colord[open < close] for open, close in zip(opens, closes) if open != -1 and close != -1] @@ -1288,13 +1275,9 @@ def volume_overlay3(ax, quotes, """ - r, g, b = colorConverter.to_rgb(colorup) - colorup = r, g, b, alpha - r, g, b = colorConverter.to_rgb(colordown) - colordown = r, g, b, alpha - colord = {True: colorup, - False: colordown, - } + colorup = mcolors.to_rgba(colorup, alpha) + colordown = mcolors.to_rgba(colordown, alpha) + colord = {True: colorup, False: colordown} dates, opens, highs, lows, closes, volumes = list(zip(*quotes)) colors = [colord[close1 >= close0] @@ -1369,8 +1352,8 @@ def index_bar(ax, vals, """ - facecolors = (colorConverter.to_rgba(facecolor, alpha),) - edgecolors = (colorConverter.to_rgba(edgecolor, alpha),) + facecolors = (mcolors.to_rgba(facecolor, alpha),) + edgecolors = (mcolors.to_rgba(edgecolor, alpha),) right = width / 2.0 left = -width / 2.0 diff --git a/lib/matplotlib/image.py b/lib/matplotlib/image.py index a29e62cc78a7..e705f2bbf94c 100644 --- a/lib/matplotlib/image.py +++ b/lib/matplotlib/image.py @@ -907,7 +907,7 @@ def make_image(self, renderer, magnification=1.0, unsampled=False): if unsampled: raise ValueError('unsampled not supported on PColorImage') fc = self.axes.patch.get_facecolor() - bg = mcolors.colorConverter.to_rgba(fc, 0) + bg = mcolors.to_rgba(fc, 0) bg = (np.array(bg)*255).astype(np.uint8) l, b, r, t = self.axes.bbox.extents width = (np.round(r) + 0.5) - (np.round(l) - 0.5) diff --git a/lib/matplotlib/legend_handler.py b/lib/matplotlib/legend_handler.py index d3574bf48190..fb3f5238c4ec 100644 --- a/lib/matplotlib/legend_handler.py +++ b/lib/matplotlib/legend_handler.py @@ -635,7 +635,7 @@ class HandlerPolyCollection(HandlerBase): """ def _update_prop(self, legend_handle, orig_handle): def first_color(colors): - colors = mcolors.colorConverter.to_rgba_array(colors) + colors = mcolors.to_rgba_array(colors) if len(colors): return colors[0] else: diff --git a/lib/matplotlib/lines.py b/lib/matplotlib/lines.py index a594ee8be774..84fcd0c38056 100644 --- a/lib/matplotlib/lines.py +++ b/lib/matplotlib/lines.py @@ -14,12 +14,11 @@ import numpy as np from numpy import ma from matplotlib import verbose -from . import artist +from . import artist, colors as mcolors from .artist import Artist from .cbook import (iterable, is_string_like, is_numlike, ls_mapper_r, pts_to_prestep, pts_to_poststep, pts_to_midstep) -from .colors import colorConverter from .path import Path from .transforms import Bbox, TransformedPath, IdentityTransform @@ -1260,24 +1259,16 @@ def update_from(self, other): other._marker.get_fillstyle()) self._drawstyle = other._drawstyle - def _get_rgb_face(self, alt=False): - facecolor = self._get_markerfacecolor(alt=alt) - if is_string_like(facecolor) and facecolor.lower() == 'none': - rgbFace = None - else: - rgbFace = colorConverter.to_rgb(facecolor) - return rgbFace - def _get_rgba_face(self, alt=False): facecolor = self._get_markerfacecolor(alt=alt) if is_string_like(facecolor) and facecolor.lower() == 'none': rgbaFace = None else: - rgbaFace = colorConverter.to_rgba(facecolor, self._alpha) + rgbaFace = mcolors.to_rgba(facecolor, self._alpha) return rgbaFace def _get_rgba_ln_color(self, alt=False): - return colorConverter.to_rgba(self._color, self._alpha) + return mcolors.to_rgba(self._color, self._alpha) # some aliases.... def set_aa(self, val): diff --git a/lib/matplotlib/mathtext.py b/lib/matplotlib/mathtext.py index 5c3e57596699..2a9a75e5347e 100644 --- a/lib/matplotlib/mathtext.py +++ b/lib/matplotlib/mathtext.py @@ -3342,12 +3342,12 @@ def to_rgba(self, texstr, color='black', dpi=120, fontsize=14): """ x, depth = self.to_mask(texstr, dpi=dpi, fontsize=fontsize) - r, g, b = mcolors.colorConverter.to_rgb(color) + r, g, b, a = mcolors.to_rgba(color) RGBA = np.zeros((x.shape[0], x.shape[1], 4), dtype=np.uint8) - RGBA[:,:,0] = int(255*r) - RGBA[:,:,1] = int(255*g) - RGBA[:,:,2] = int(255*b) - RGBA[:,:,3] = x + RGBA[:, :, 0] = 255 * r + RGBA[:, :, 1] = 255 * g + RGBA[:, :, 2] = 255 * b + RGBA[:, :, 3] = x return RGBA, depth def to_png(self, filename, texstr, color='black', dpi=120, fontsize=14): diff --git a/lib/matplotlib/patches.py b/lib/matplotlib/patches.py index 7aebc7c5043a..fa22b681976a 100644 --- a/lib/matplotlib/patches.py +++ b/lib/matplotlib/patches.py @@ -278,7 +278,7 @@ def set_edgecolor(self, color): if color is None: color = mpl.rcParams['patch.edgecolor'] self._original_edgecolor = color - self._edgecolor = colors.colorConverter.to_rgba(color, self._alpha) + self._edgecolor = colors.to_rgba(color, self._alpha) self.stale = True def set_ec(self, color): @@ -295,7 +295,7 @@ def set_facecolor(self, color): color = mpl.rcParams['patch.facecolor'] # save: otherwise changing _fill may lose alpha information self._original_facecolor = color - self._facecolor = colors.colorConverter.to_rgba(color, self._alpha) + self._facecolor = colors.to_rgba(color, self._alpha) if not self._fill: self._facecolor = list(self._facecolor) self._facecolor[3] = 0 @@ -585,8 +585,7 @@ def _update(self): if self.props is not None: self.update(self.props) else: - r, g, b, a = colors.colorConverter.to_rgba( - self.patch.get_facecolor()) + r, g, b, a = colors.to_rgba(self.patch.get_facecolor()) rho = 0.3 r = rho * r g = rho * g diff --git a/lib/matplotlib/patheffects.py b/lib/matplotlib/patheffects.py index 5435bcc8bd00..a0c8933b9b86 100644 --- a/lib/matplotlib/patheffects.py +++ b/lib/matplotlib/patheffects.py @@ -10,9 +10,8 @@ from matplotlib.externals import six from matplotlib.backend_bases import RendererBase -import matplotlib.transforms as mtransforms -from matplotlib.colors import colorConverter -import matplotlib.patches as mpatches +from matplotlib import ( + colors as mcolors, patches as mpatches, transforms as mtransforms) class AbstractPathEffect(object): @@ -241,7 +240,7 @@ def __init__(self, offset=(2, -2), if shadow_rgbFace is None: self._shadow_rgbFace = shadow_rgbFace else: - self._shadow_rgbFace = colorConverter.to_rgba(shadow_rgbFace) + self._shadow_rgbFace = mcolors.to_rgba(shadow_rgbFace) if alpha is None: alpha = 0.3 @@ -322,7 +321,7 @@ def __init__(self, offset=(2,-2), if shadow_color is None: self._shadow_color = shadow_color else: - self._shadow_color = colorConverter.to_rgba(shadow_color) + self._shadow_color = mcolors.to_rgba(shadow_color) self._alpha = alpha self._rho = rho diff --git a/lib/matplotlib/tests/test_cbook.py b/lib/matplotlib/tests/test_cbook.py index 26d96e6dc97c..fe0377a72eba 100644 --- a/lib/matplotlib/tests/test_cbook.py +++ b/lib/matplotlib/tests/test_cbook.py @@ -86,7 +86,7 @@ def setUp(self): datetime(2008, 1, 5), datetime(2008, 1, 6)] self.arr_dt2 = np.array(self.arr_dt) self.arr_colors = ['r', 'g', 'b', 'c', 'm', 'y'] - self.arr_rgba = mcolors.colorConverter.to_rgba_array(self.arr_colors) + self.arr_rgba = mcolors.to_rgba_array(self.arr_colors) @raises(ValueError) def test_bad_first_arg(self): diff --git a/lib/matplotlib/tests/test_colors.py b/lib/matplotlib/tests/test_colors.py index 737ef23053d9..b980b9753831 100644 --- a/lib/matplotlib/tests/test_colors.py +++ b/lib/matplotlib/tests/test_colors.py @@ -260,7 +260,7 @@ def test_cmap_and_norm_from_levels_and_colors(): def test_cmap_and_norm_from_levels_and_colors2(): levels = [-1, 2, 2.5, 3] colors = ['red', (0, 1, 0), 'blue', (0.5, 0.5, 0.5), (0.0, 0.0, 0.0, 1.0)] - clr = mcolors.colorConverter.to_rgba_array(colors) + clr = mcolors.to_rgba_array(colors) bad = (0.1, 0.1, 0.1, 0.1) no_color = (0.0, 0.0, 0.0, 0.0) masked_value = 'masked_value' @@ -337,13 +337,9 @@ def test_autoscale_masked(): def test_colors_no_float(): # Gray must be a string to distinguish 3-4 grays from RGB or RGBA. - def gray_from_float_rgb(): - return mcolors.colorConverter.to_rgb(0.4) - def gray_from_float_rgba(): - return mcolors.colorConverter.to_rgba(0.4) + return mcolors.to_rgba(0.4) - assert_raises(ValueError, gray_from_float_rgb) assert_raises(ValueError, gray_from_float_rgba) @@ -559,12 +555,8 @@ def angled_plane(azimuth, elevation, angle, x, y): def test_xkcd(): - x11_blue = mcolors.rgb2hex( - mcolors.colorConverter.to_rgb('blue')) - assert x11_blue == '#0000ff' - XKCD_blue = mcolors.rgb2hex( - mcolors.colorConverter.to_rgb('xkcd:blue')) - assert XKCD_blue == '#0343df' + assert mcolors.to_hex("blue") == "#0000ffff" + assert mcolors.to_hex("xkcd:blue") == "#0343dfff" def _sph2cart(theta, phi): @@ -615,17 +607,13 @@ def test_colormap_reversing(): def test_cn(): matplotlib.rcParams['axes.prop_cycle'] = cycler('color', ['blue', 'r']) - x11_blue = mcolors.rgb2hex(mcolors.colorConverter.to_rgb('C0')) - assert x11_blue == '#0000ff' - red = mcolors.rgb2hex(mcolors.colorConverter.to_rgb('C1')) - assert red == '#ff0000' + assert mcolors.to_hex("C0") == '#0000ffff' + assert mcolors.to_hex("C1") == '#ff0000ff' matplotlib.rcParams['axes.prop_cycle'] = cycler('color', ['xkcd:blue', 'r']) - XKCD_blue = mcolors.rgb2hex(mcolors.colorConverter.to_rgb('C0')) - assert XKCD_blue == '#0343df' - red = mcolors.rgb2hex(mcolors.colorConverter.to_rgb('C1')) - assert red == '#ff0000' + assert mcolors.to_hex("C0") == '#0343df' + assert mcolors.to_hex("C1") == '#ff0000' def test_conversions(): diff --git a/lib/matplotlib/tests/test_rcparams.py b/lib/matplotlib/tests/test_rcparams.py index d37adfc2b00d..6b759aa9a509 100644 --- a/lib/matplotlib/tests/test_rcparams.py +++ b/lib/matplotlib/tests/test_rcparams.py @@ -202,15 +202,11 @@ def test_legend_facecolor(): get_func = 'get_facecolor' rcparam = 'legend.facecolor' test_values = [({rcparam: 'r'}, - mcolors.colorConverter.to_rgba('r')), - ({rcparam: 'inherit', - 'axes.facecolor': 'r' - }, - mcolors.colorConverter.to_rgba('r')), - ({rcparam: 'g', - 'axes.facecolor': 'r'}, - mcolors.colorConverter.to_rgba('g')) - ] + mcolors.to_rgba('r')), + ({rcparam: 'inherit', 'axes.facecolor': 'r'}, + mcolors.to_rgba('r')), + ({rcparam: 'g', 'axes.facecolor': 'r'}, + mcolors.to_rgba('g'))] for rc_dict, target in test_values: yield _legend_rcparam_helper, rc_dict, target, get_func @@ -219,15 +215,11 @@ def test_legend_edgecolor(): get_func = 'get_edgecolor' rcparam = 'legend.edgecolor' test_values = [({rcparam: 'r'}, - mcolors.colorConverter.to_rgba('r')), - ({rcparam: 'inherit', - 'axes.edgecolor': 'r' - }, - mcolors.colorConverter.to_rgba('r')), - ({rcparam: 'g', - 'axes.facecolor': 'r'}, - mcolors.colorConverter.to_rgba('g')) - ] + mcolors.to_rgba('r')), + ({rcparam: 'inherit', 'axes.edgecolor': 'r'}, + mcolors.to_rgba('r')), + ({rcparam: 'g', 'axes.facecolor': 'r'}, + mcolors.to_rgba('g'))] for rc_dict, target in test_values: yield _legend_rcparam_helper, rc_dict, target, get_func diff --git a/lib/mpl_toolkits/mplot3d/art3d.py b/lib/mpl_toolkits/mplot3d/art3d.py index 027d6a07eb76..b8a4619959ef 100755 --- a/lib/mpl_toolkits/mplot3d/art3d.py +++ b/lib/mpl_toolkits/mplot3d/art3d.py @@ -389,12 +389,12 @@ def do_3d_projection(self, renderer): fcs = (zalpha(self._facecolor3d, vzs) if self._depthshade else self._facecolor3d) - fcs = mcolors.colorConverter.to_rgba_array(fcs, self._alpha) + fcs = mcolors.to_rgba_array(fcs, self._alpha) self.set_facecolors(fcs) ecs = (zalpha(self._edgecolor3d, vzs) if self._depthshade else self._edgecolor3d) - ecs = mcolors.colorConverter.to_rgba_array(ecs, self._alpha) + ecs = mcolors.to_rgba_array(ecs, self._alpha) self.set_edgecolors(ecs) PatchCollection.set_offsets(self, list(zip(vxs, vys))) @@ -457,12 +457,12 @@ def do_3d_projection(self, renderer): fcs = (zalpha(self._facecolor3d, vzs) if self._depthshade else self._facecolor3d) - fcs = mcolors.colorConverter.to_rgba_array(fcs, self._alpha) + fcs = mcolors.to_rgba_array(fcs, self._alpha) self.set_facecolors(fcs) ecs = (zalpha(self._edgecolor3d, vzs) if self._depthshade else self._edgecolor3d) - ecs = mcolors.colorConverter.to_rgba_array(ecs, self._alpha) + ecs = mcolors.to_rgba_array(ecs, self._alpha) self.set_edgecolors(ecs) PathCollection.set_offsets(self, list(zip(vxs, vys))) @@ -684,12 +684,12 @@ def set_alpha(self, alpha): raise TypeError('alpha must be a float or None') artist.Artist.set_alpha(self, alpha) try: - self._facecolors = mcolors.colorConverter.to_rgba_array( + self._facecolors = mcolors.to_rgba_array( self._facecolors3d, self._alpha) except (AttributeError, TypeError, IndexError): pass try: - self._edgecolors = mcolors.colorConverter.to_rgba_array( + self._edgecolors = mcolors.to_rgba_array( self._edgecolors3d, self._alpha) except (AttributeError, TypeError, IndexError): pass @@ -764,7 +764,7 @@ def get_colors(c, num): """Stretch the color argument to provide the required number num""" if type(c) == type("string"): - c = mcolors.colorConverter.to_rgba(c) + c = mcolors.to_rgba(c) if iscolor(c): return [c] * num diff --git a/lib/mpl_toolkits/mplot3d/axes3d.py b/lib/mpl_toolkits/mplot3d/axes3d.py index 283c17c6ea4d..b416446c2e07 100755 --- a/lib/mpl_toolkits/mplot3d/axes3d.py +++ b/lib/mpl_toolkits/mplot3d/axes3d.py @@ -29,7 +29,8 @@ import matplotlib.scale as mscale from matplotlib.tri.triangulation import Triangulation import numpy as np -from matplotlib.colors import Normalize, colorConverter, LightSource +from matplotlib import colors as mcolors +from matplotlib.colors import Normalize, LightSource from . import art3d from . import proj3d @@ -1596,7 +1597,7 @@ def plot_surface(self, X, Y, Z, *args, **kwargs): color = kwargs.pop('color', None) if color is None: color = self._get_lines.get_next_color() - color = np.array(colorConverter.to_rgba(color)) + color = np.array(mcolors.to_rgba(color)) fcolors = None cmap = kwargs.get('cmap', None) @@ -1714,7 +1715,7 @@ def _shade_colors(self, color, normals): if len(shade[mask]) > 0: norm = Normalize(min(shade[mask]), max(shade[mask])) shade[~mask] = min(shade[mask]) - color = colorConverter.to_rgba_array(color) + color = mcolors.to_rgba_array(color) # shape of color should be (M, 4) (where M is number of faces) # shape of shade should be (M,) # colors should have final shape of (M, 4) @@ -1868,7 +1869,7 @@ def plot_trisurf(self, *args, **kwargs): color = kwargs.pop('color', None) if color is None: color = self._get_lines.get_next_color() - color = np.array(colorConverter.to_rgba(color)) + color = np.array(mcolors.to_rgba(color)) cmap = kwargs.get('cmap', None) norm = kwargs.pop('norm', None) @@ -2450,7 +2451,7 @@ def bar3d(self, x, y, z, dx, dy, dz, color=None, facecolors.extend([c] * 6) else: # a single color specified, or face colors specified explicitly - facecolors = list(colorConverter.to_rgba_array(color)) + facecolors = list(mcolors.to_rgba_array(color)) if len(facecolors) < len(x): facecolors *= (6 * len(x)) From bc6ad2a455d761ed85c87cbc6678e4a4a0eb2fd3 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Mon, 9 May 2016 07:35:15 -0700 Subject: [PATCH 07/11] Base colors are not special. --- examples/color/named_colors.py | 2 +- lib/matplotlib/_color_data.py | 12 ++++++++++++ lib/matplotlib/colors.py | 20 +++++++------------- 3 files changed, 20 insertions(+), 14 deletions(-) diff --git a/examples/color/named_colors.py b/examples/color/named_colors.py index 65d8b541205e..232b514eda30 100644 --- a/examples/color/named_colors.py +++ b/examples/color/named_colors.py @@ -14,7 +14,7 @@ from matplotlib import colors as mcolors -colors = mcolors.CSS4_COLORS +colors = dict(mcolors.BASE_COLORS, **mcolors.CSS4_COLORS) # Sort by hue, saturation, value and name. by_hsv = sorted((tuple(mcolors.rgb_to_hsv(mcolors.to_rgba(color)[:3])), name) diff --git a/lib/matplotlib/_color_data.py b/lib/matplotlib/_color_data.py index f19c3e5d4af6..5d6aae709971 100644 --- a/lib/matplotlib/_color_data.py +++ b/lib/matplotlib/_color_data.py @@ -3,6 +3,18 @@ from matplotlib.externals import six + +BASE_COLORS = { + 'b': (0, 0, 1), + 'g': (0, 0.5, 0), + 'r': (1, 0, 0), + 'c': (0, 0.75, 0.75), + 'm': (0.75, 0, 0.75), + 'y': (0.75, 0.75, 0), + 'k': (0, 0, 0), + 'w': (1, 1, 1)} + + # This mapping of color names -> hex values is taken from # a survey run by Randel Monroe see: # http://blog.xkcd.com/2010/05/03/color-survey-results/ diff --git a/lib/matplotlib/colors.py b/lib/matplotlib/colors.py index 34f5ca81050e..d4265a763be2 100644 --- a/lib/matplotlib/colors.py +++ b/lib/matplotlib/colors.py @@ -65,13 +65,13 @@ import numpy as np from numpy import ma import matplotlib.cbook as cbook -from ._color_data import XKCD_COLORS, CSS4_COLORS +from ._color_data import BASE_COLORS, CSS4_COLORS, XKCD_COLORS class _ColorMapping(dict): - def __init__(self, mapping, cache): + def __init__(self, mapping): super(_ColorMapping, self).__init__(mapping) - self._cache = cache + self._cache = {} def __setitem__(self, key, value): super(_ColorMapping, self).__setitem__(key, value) @@ -82,18 +82,12 @@ def __delitem__(self, key, value): self._cache.clear() -_colors_full_map = { - 'b': (0, 0, 1), - 'g': (0, 0.5, 0), - 'r': (1, 0, 0), - 'c': (0, 0.75, 0.75), - 'm': (0.75, 0, 0.75), - 'y': (0.75, 0.75, 0), - 'k': (0, 0, 0), - 'w': (1, 1, 1)} +_colors_full_map = {} +# Set by reverse priority order. _colors_full_map.update(XKCD_COLORS) _colors_full_map.update(CSS4_COLORS) -_colors_full_map = _ColorMapping(_colors_full_map, {}) +_colors_full_map.update(BASE_COLORS) +_colors_full_map = _ColorMapping(_colors_full_map) def get_named_colors_mapping(): From d9db0627438692ae86c28b9707183ef6ef5de1bb Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Tue, 10 May 2016 21:44:19 -0700 Subject: [PATCH 08/11] Minor changes to colors.py. --- lib/matplotlib/colors.py | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/lib/matplotlib/colors.py b/lib/matplotlib/colors.py index d4265a763be2..275e8d83766f 100644 --- a/lib/matplotlib/colors.py +++ b/lib/matplotlib/colors.py @@ -71,15 +71,15 @@ class _ColorMapping(dict): def __init__(self, mapping): super(_ColorMapping, self).__init__(mapping) - self._cache = {} + self.cache = {} def __setitem__(self, key, value): super(_ColorMapping, self).__setitem__(key, value) - self._cache.clear() + self.cache.clear() def __delitem__(self, key, value): super(_ColorMapping, self).__delitem__(key, value) - self._cache.clear() + self.cache.clear() _colors_full_map = {} @@ -130,14 +130,14 @@ def to_rgba(c, alpha=None): if prop_cycler is None and 'axes.color_cycle' in rcParams: clist = rcParams['axes.color_cycle'] prop_cycler = cycler('color', clist) - colors = prop_cycler._transpose()['color'] + colors = prop_cycler._transpose().get('color', 'k') c = colors[int(c[1]) % len(colors)] try: - rgba = _colors_full_map._cache[c, alpha] + rgba = _colors_full_map.cache[c, alpha] except (KeyError, TypeError): # Not in cache, or unhashable. rgba = _to_rgba_no_colorcycle(c, alpha) try: - _colors_full_map._cache[c, alpha] = rgba + _colors_full_map.cache[c, alpha] = rgba except TypeError: pass return rgba @@ -233,6 +233,12 @@ def to_rgba_array(c, alpha=None): return result +def to_rgb(c): + """Convert `c` to an RGB color, silently dropping the alpha channel. + """ + return to_rgba(c)[:3] + + def to_hex(c, alpha=None): """Convert `c` to a hex color. @@ -274,7 +280,7 @@ class ColorConverter(object): """ colors = _colors_full_map - cache = _colors_full_map._cache + cache = _colors_full_map.cache @staticmethod def to_rgb(arg): @@ -292,7 +298,7 @@ def to_rgb(arg): if *arg* is *RGBA*, the *A* will simply be discarded. """ - return to_rgba(arg)[:3] + return to_rgb(arg) @staticmethod def to_rgba(arg, alpha=None): From cb3d7c9929e97581cfdc9f72d52c1f167265949d Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Wed, 11 May 2016 16:16:26 -0700 Subject: [PATCH 09/11] Update docs for RGBA support. --- doc/conf.py | 16 +++++++++++++++- lib/matplotlib/colors.py | 27 ++++++++++++++------------- 2 files changed, 29 insertions(+), 14 deletions(-) diff --git a/doc/conf.py b/doc/conf.py index c0aea51fb761..6bef97d750cc 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -334,5 +334,19 @@ def getapi(*args): sys.modules['sip'] = mocksip sys.modules['PyQt4'] = mockpyqt4 -################# numpydoc config #################### +# numpydoc config + numpydoc_show_class_members = False + +# Skip deprecated members + +def skip_deprecated(app, what, name, obj, skip, options): + if skip: + return skip + skipped = {"matplotlib.colors": ["ColorConverter", "hex2color", "rgb2hex"]} + skip_list = skipped.get(getattr(obj, "__module__", None)) + if skip_list is not None: + return getattr(obj, "__name__", None) in skip_list + +def setup(app): + app.connect('autodoc-skip-member', skip_deprecated) diff --git a/lib/matplotlib/colors.py b/lib/matplotlib/colors.py index 275e8d83766f..a1a155b321c7 100644 --- a/lib/matplotlib/colors.py +++ b/lib/matplotlib/colors.py @@ -17,21 +17,21 @@ The module also provides functions for checking whether an object can be interpreted as a color (:func:`is_color_like`), for converting such an object -to an RGBA tuple (:func:`to_rgba`) or to an HTML hex string (:func:`to_hex`), -and a sequence of colors to an `(n, 4)` RGBA array (:func:`to_rgba_array`). -Caching is used for efficiency. +to an RGBA tuple (:func:`to_rgba`) or to an HTML-like hex string in the +`#rrggbbaa` format (:func:`to_hex`), and a sequence of colors to an `(n, 4)` +RGBA array (:func:`to_rgba_array`). Caching is used for efficiency. Commands which take color arguments can use several formats to specify the colors. For the basic built-in colors, you can use a single letter - - b: blue - - g: green - - r: red - - c: cyan - - m: magenta - - y: yellow - - k: black - - w: white + - `b`: blue + - `g`: green + - `r`: red + - `c`: cyan + - `m`: magenta + - `y`: yellow + - `k`: black + - `w`: white To use the colors that are part of the active color cycle in the current style, use `C` followed by a digit. For example: @@ -48,8 +48,9 @@ color = '#eeefff' -or you can pass an *R* , *G* , *B* tuple, where each of *R* , *G* , *B* are in -the range [0,1]. +(possibly specifying an alpha value as well), or you can pass an `(r, g, b)` +or `(r, g, b, a)` tuple, where each of `r`, `g`, `b` and `a` are in the range +[0,1]. Finally, legal html names for colors, like 'red', 'burlywood' and 'chartreuse' are supported. From d492ba4604e4157219195bb49c6e1310cd4f345e Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Wed, 11 May 2016 20:55:44 -0700 Subject: [PATCH 10/11] Add test for #rrggbbaa roundtrip; add whatsnew. --- doc/users/whats_new/rgba-support.rst | 11 +++++++++++ lib/matplotlib/tests/test_colors.py | 3 +++ 2 files changed, 14 insertions(+) create mode 100644 doc/users/whats_new/rgba-support.rst diff --git a/doc/users/whats_new/rgba-support.rst b/doc/users/whats_new/rgba-support.rst new file mode 100644 index 000000000000..9a844e3825ae --- /dev/null +++ b/doc/users/whats_new/rgba-support.rst @@ -0,0 +1,11 @@ +Improved color conversion API and RGBA support +---------------------------------------------- + +The :module:`~matplotlib.colors` gained a new color conversion API with +full support for the alpha channel. The main public functions are +:func:`~matplotlib.colors.is_color_like`, :func:`matplotlib.colors.to_rgba`, +:func:`matplotlib.colors.to_rgba_array` and :func:`~matplotlib.colors.to_hex`. +RGBA quadruplets are encoded in hex format as `#rrggbbaa`. + +A side benefit is that the Qt options editor now allows setting the alpha +channel of the artists as well. diff --git a/lib/matplotlib/tests/test_colors.py b/lib/matplotlib/tests/test_colors.py index b980b9753831..92e1cbf7356d 100644 --- a/lib/matplotlib/tests/test_colors.py +++ b/lib/matplotlib/tests/test_colors.py @@ -623,6 +623,9 @@ def test_conversions(): assert_equal(mcolors.to_rgba((1, 1, 1), .5), (1, 1, 1, .5)) # builtin round differs between py2 and py3. assert_equal(mcolors.to_hex((.7, .7, .7)), "#b2b2b2ff") + # hex roundtrip. + hex_color = "#1234abcd" + assert_equal(mcolors.to_hex(mcolors.to_rgba(hex_color)), hex_color) if __name__ == '__main__': From 7c2ec4c050d418fe39c87df6393ecef1326ed0ef Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Wed, 11 May 2016 21:19:22 -0700 Subject: [PATCH 11/11] Default to `#rrggbb`, not `#rrggbbaa`. --- doc/users/colors.rst | 2 +- lib/matplotlib/colors.py | 14 +++++++++----- lib/matplotlib/tests/test_colors.py | 13 +++++++------ 3 files changed, 17 insertions(+), 12 deletions(-) diff --git a/doc/users/colors.rst b/doc/users/colors.rst index f935edd5c935..4ae509601351 100644 --- a/doc/users/colors.rst +++ b/doc/users/colors.rst @@ -9,7 +9,7 @@ it can be provided as: * ``(r, g, b)`` tuples * ``(r, g, b, a)`` tuples -* hex string, ex ``#OFOFOF`` +* hex string, ex ``#0F0F0F``, or ``#0F0F0F0F`` (with alpha channel) * float value between [0, 1] for gray level * One of ``{'b', 'g', 'r', 'c', 'm', 'y', 'k', 'w'}`` * valid CSS4/X11 color names diff --git a/lib/matplotlib/colors.py b/lib/matplotlib/colors.py index a1a155b321c7..680a7bf12c51 100644 --- a/lib/matplotlib/colors.py +++ b/lib/matplotlib/colors.py @@ -18,7 +18,7 @@ The module also provides functions for checking whether an object can be interpreted as a color (:func:`is_color_like`), for converting such an object to an RGBA tuple (:func:`to_rgba`) or to an HTML-like hex string in the -`#rrggbbaa` format (:func:`to_hex`), and a sequence of colors to an `(n, 4)` +`#rrggbb` format (:func:`to_hex`), and a sequence of colors to an `(n, 4)` RGBA array (:func:`to_rgba_array`). Caching is used for efficiency. Commands which take color arguments can use several formats to specify @@ -240,13 +240,17 @@ def to_rgb(c): return to_rgba(c)[:3] -def to_hex(c, alpha=None): +def to_hex(c, keep_alpha=False): """Convert `c` to a hex color. - If `alpha` is not `None`, it forces the alpha value. + Uses the #rrggbb format if `keep_alpha` is False (the default), `#rrggbbaa` + otherwise. """ + c = to_rgba(c) + if not keep_alpha: + c = c[:3] return "#" + "".join(format(int(np.round(val * 255)), "02x") - for val in to_rgba(c, alpha=alpha)) + for val in c) ### Backwards-compatible color-conversion API @@ -258,7 +262,7 @@ def to_hex(c, alpha=None): def rgb2hex(c): 'Given an rgb or rgba sequence of 0-1 floats, return the hex string' - return to_hex(c)[:7] # Drop alpha. + return to_hex(c) def hex2color(c): diff --git a/lib/matplotlib/tests/test_colors.py b/lib/matplotlib/tests/test_colors.py index 92e1cbf7356d..f24f4bc64f77 100644 --- a/lib/matplotlib/tests/test_colors.py +++ b/lib/matplotlib/tests/test_colors.py @@ -555,8 +555,8 @@ def angled_plane(azimuth, elevation, angle, x, y): def test_xkcd(): - assert mcolors.to_hex("blue") == "#0000ffff" - assert mcolors.to_hex("xkcd:blue") == "#0343dfff" + assert mcolors.to_hex("blue") == "#0000ff" + assert mcolors.to_hex("xkcd:blue") == "#0343df" def _sph2cart(theta, phi): @@ -607,8 +607,8 @@ def test_colormap_reversing(): def test_cn(): matplotlib.rcParams['axes.prop_cycle'] = cycler('color', ['blue', 'r']) - assert mcolors.to_hex("C0") == '#0000ffff' - assert mcolors.to_hex("C1") == '#ff0000ff' + assert mcolors.to_hex("C0") == '#0000ff' + assert mcolors.to_hex("C1") == '#ff0000' matplotlib.rcParams['axes.prop_cycle'] = cycler('color', ['xkcd:blue', 'r']) @@ -622,10 +622,11 @@ def test_conversions(): # alpha is properly set. assert_equal(mcolors.to_rgba((1, 1, 1), .5), (1, 1, 1, .5)) # builtin round differs between py2 and py3. - assert_equal(mcolors.to_hex((.7, .7, .7)), "#b2b2b2ff") + assert_equal(mcolors.to_hex((.7, .7, .7)), "#b2b2b2") # hex roundtrip. hex_color = "#1234abcd" - assert_equal(mcolors.to_hex(mcolors.to_rgba(hex_color)), hex_color) + assert_equal(mcolors.to_hex(mcolors.to_rgba(hex_color), keep_alpha=True), + hex_color) if __name__ == '__main__': pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

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:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy