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/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/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/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/color/named_colors.py b/examples/color/named_colors.py index 82ae356031d0..232b514eda30 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 = dict(mcolors.BASE_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) 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/_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/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/colors.py b/lib/matplotlib/colors.py index 5c5fa7fd83c8..680a7bf12c51 100644 --- a/lib/matplotlib/colors.py +++ b/lib/matplotlib/colors.py @@ -15,21 +15,23 @@ :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-like hex string in the +`#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 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: @@ -44,14 +46,16 @@ 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]. +(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 __future__ import (absolute_import, division, print_function, unicode_literals) import re @@ -62,55 +66,211 @@ 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 -# for back copatibility -cnames = CSS4_COLORS -COLOR_NAMES = {'xkcd': XKCD_COLORS, - 'css4': CSS4_COLORS} +class _ColorMapping(dict): + def __init__(self, mapping): + super(_ColorMapping, self).__init__(mapping) + self.cache = {} + def __setitem__(self, key, value): + super(_ColorMapping, self).__setitem__(key, value) + self.cache.clear() -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 + def __delitem__(self, key, value): + super(_ColorMapping, self).__delitem__(key, value) + self.cache.clear() - try: - colorConverter.to_rgb(c) + +_colors_full_map = {} +# Set by reverse priority order. +_colors_full_map.update(XKCD_COLORS) +_colors_full_map.update(CSS4_COLORS) +_colors_full_map.update(BASE_COLORS) +_colors_full_map = _ColorMapping(_colors_full_map) + + +def get_named_colors_mapping(): + """Return the global mapping of names to named colors. + """ + return _colors_full_map + + +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. + + 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): + 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().get('color', 'k') + c = colors[int(c[1]) % len(colors)] + try: + 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 + 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. + + If `alpha` is not `None`, it forces the 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. + # Python 2.7 / numpy 1.6 apparently require this to return builtin floats, + # not numpy floats. + try: + c = tuple(map(float, c)) + except TypeError: + raise ValueError("Invalid RGBA argument: {!r}".format(orig_c)) + 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. + + 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? + 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 + # 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) + 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, keep_alpha=False): + """Convert `c` to a hex color. + + 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 c) + + +### 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) + + +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 +283,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']] + colors = _colors_full_map + cache = _colors_full_map.cache - @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])) - - 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 +303,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. + return to_rgb(arg) - 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 - - 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 +316,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 +328,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/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 193ce0094c15..f24f4bc64f77 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") == "#0000ff" + assert mcolors.to_hex("xkcd:blue") == "#0343df" def _sph2cart(theta, phi): @@ -615,17 +607,26 @@ 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") == '#0000ff' + assert mcolors.to_hex("C1") == '#ff0000' 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(): + # 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)) + # builtin round differs between py2 and py3. + assert_equal(mcolors.to_hex((.7, .7, .7)), "#b2b2b2") + # hex roundtrip. + hex_color = "#1234abcd" + assert_equal(mcolors.to_hex(mcolors.to_rgba(hex_color), keep_alpha=True), + hex_color) if __name__ == '__main__': 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))