# -*- python -*-
# -*- coding: utf-8 -*-
#
#  This file is part of the colormap software
#
#  Copyright (c) 2011-20134
#
#  File author(s): Thomas Cokelaer <cokelaer@gmail.com>
#
#  Distributed under the GPLv3 License.
#  See accompanying file LICENSE.txt or copy at
#      http://www.gnu.org/licenses/gpl-3.0.html
#
#  Website: https://github.com/cokelaer/colormap
#  Documentation: http://packages.python.org/colormap
#
##############################################################################
"""Utilities provided in this module can be found either in the
standard Python module called :mod:`colorsys` or in matplotlib.colors
(e.g rgb2hex) or are original to this module (e.g., rgb2huv)


"""
# matplotlib dependence is only inside Colormap class
import colorsys
from easydev.tools import check_param_in_list, swapdict, check_range
from colormap.xfree86 import XFree86_colors


__all__ = ["HEX", "Color", "hex2web", "web2hex", "hex2rgb", "hex2dec",
    "rgb2hex", "rgb2hsv", "hsv2rgb", "rgb2hls", "hls2rgb","yuv2rgb", "rgb2yuv",
    "to_intensity", "yuv2rgb_int", "rgb2yuv_int", "Colormap"
    ]


def hex2web(hexa):
    """Convert hexadecimal string (6 digits) into *web* version (3 digits)

    .. doctest::

        >>> from colormap.colors import hex2web
        >>> hex2web("#FFAA11")
        '#FA1'

    .. seealso:: :func:`web2hex`, :func:`hex2rgb`
        :func:`rgb2hex`, :func:`rgb2hsv`, :func:`hsv2rgb`, :func:`rgb2hls`,
        :func:`hls2rgb`
    """
    hexa = HEX().get_standard_hex_color(hexa)
    return "#" + hexa[1::2]


def web2hex(web):
    """Convert *web* hexadecimal string (3 digits) into  6 digits version

    .. doctest::

        >>> from colormap.colors import web2hex
        >>> web2hex("#FA1")
        '#FFAA11'

    .. seealso:: :func:`hex2web`, :func:`hex2rgb`
        :func:`rgb2hex`, :func:`rgb2hsv`, :func:`hsv2rgb`, :func:`rgb2hls`,
        :func:`hls2rgb`
    """
    return HEX().get_standard_hex_color(web)


def hex2rgb(hexcolor, normalise=False):
    """This function converts a hex color triplet into RGB

    Valid hex code are:

     * #FFF
     * #0000FF
     * 0x0000FF
     * 0xFA1


    .. doctest::

        >>> from colormap.colors import hex2rgb
        >>> hex2rgb("#FFF", normalise=False)
        (255, 255, 255)
        >>> hex2rgb("#FFFFFF", normalise=True)
        (1.0, 1.0, 1.0)


    .. seealso:: :func:`hex2web`, :func:`web2hex`,
        :func:`rgb2hex`, :func:`rgb2hsv`, :func:`hsv2rgb`, :func:`rgb2hls`,
        :func:`hls2rgb`
    """
    hexcolor = HEX().get_standard_hex_color(hexcolor)[1:]
    r, g, b =  int(hexcolor[0:2], 16), int(hexcolor[2:4], 16), int(hexcolor[4:6], 16)
    if normalise:
        r, g, b = _normalise(r, g, b)
    return r, g, b


def rgb2hex(r, g, b, normalised=False):
    """Convert RGB to hexadecimal color

    :param: can be a tuple/list/set of 3 values (R,G,B)
    :return: a hex vesion ofthe RGB 3-tuple

    .. doctest::

        >>> from colormap.colors import rgb2hex
        >>> rgb2hex(0,0,255, normalised=False)
        '#0000FF'
        >>> rgb2hex(0,0,1, normalised=True)
        '#0000FF'

    .. seealso:: :func:`hex2web`, :func:`web2hex`, :func:`hex2rgb`
        , :func:`rgb2hsv`, :func:`hsv2rgb`, :func:`rgb2hls`,
        :func:`hls2rgb`

    """
    if normalised:
        r, g, b = _denormalise(r, g, b, mode="rgb")
        r = int(r)
        g = int(g)
        b = int(b)
    check_range(r, 0, 255)
    check_range(g, 0, 255)
    check_range(b, 0, 255)
    return '#%02X%02X%02X' % (r, g, b)


def rgb2hls(r, g, b, normalised=True):
    """Convert an RGB value to an HLS value.

    :param bool normalised: if *normalised* is True, the input RGB triplet
        should be in the range 0-1 (0-255 otherwise)
    :return: the HLS triplet. If *normalised* parameter is True, the output
        triplet is in the range 0-1; otherwise, H in the range 0-360 and LS
        in the range 0-100.

    .. doctest::

        >>> from colormap.colors import rgb2hls
        >>> rgb2hls(255,255,255, normalised=False)
        (0.0, 1.0, 0.0)


    .. seealso:: :func:`hex2web`, :func:`web2hex`, :func:`hex2rgb`
        :func:`rgb2hex`, :func:`hsv2rgb`,
        :func:`hls2rgb`
    """
    # rgb_to_hsv expects normalised values !
    if normalised:
        upper = 1
    else:
        upper = 255
    check_range(r, 0, upper)
    check_range(g, 0, upper)
    check_range(b, 0, upper)
    if normalised==False:
        r, g, b = _normalise(r, g, b)
    h, l, s = colorsys.rgb_to_hls(r, g, b)
    return h, l, s


def rgb2hsv(r, g, b, normalised=True):
    """Convert an RGB value to an HSV value.

    :param bool normalised: if *normalised* is True, the input RGB triplet
        should be in the range 0-1 (0-255 otherwise)
    :return: the HSV triplet. If *normalised* parameter is True, the output
        triplet is in the range 0-1; otherwise, H in the range 0-360 and LS
        in the range 0-100.

    .. doctest::

        >>> from colormap.colors import rgb2hsv
        >>> rgb2hsv(0.5,0,1)
        (0.75, 1, 1)


    .. seealso:: :func:`hex2web`, :func:`web2hex`, :func:`hex2rgb`
        :func:`rgb2hex`, :func:`hsv2rgb`, :func:`rgb2hls`,
        :func:`hls2rgb`
    """
    # rgb_to_hsv expects normalised values !
    if normalised:
        upper = 1
    else:
        upper = 255
    check_range(r, 0, upper)
    check_range(g, 0, upper)
    check_range(b, 0, upper)
    if normalised==False:
        r, g, b = _normalise(r, g, b)
    h, s, v = colorsys.rgb_to_hsv(r, g, b)
    return h,s,v


def hsv2rgb(h, s, v, normalised=True):
    """Convert a hue-saturation-value (HSV) value to a red-green-blue (RGB).

    :param bool normalised: If *normalised* is True, the input HSV triplet
        should be in the range 0-1; otherwise, H in the range 0-360 and LS
        in the range 0-100.
    :return: the RGB triplet. The output
        triplet is in the range 0-1 whether the input is normalised or not.

    .. doctest::

        >>> from colormap.colors import hsv2rgb
        >>> hsv2rgb(0.5,1,1, normalised=True)  # doctest: +SKIP
        (0, 1, 1)


    .. seealso:: :func:`hex2web`, :func:`web2hex`, :func:`hex2rgb`
        :func:`rgb2hex`, :func:`rgb2hsv`, :func:`rgb2hls`,
        :func:`hls2rgb`
    .. seealso:: :func:`rgb2hex`
    """
    if normalised:
        upper = 1
    else:
        upper = 100
    if normalised:
        uppera = 1
    else:
        uppera = 360
    check_range(h, 0, uppera)
    check_range(s, 0, upper)
    check_range(v, 0, upper)
    if normalised == False:
        h, s, v = _normalise(h, s, v, mode="hsv")
    return colorsys.hsv_to_rgb(h, s, v)


def hls2rgb(h, l, s, normalised=True):
    """Convert an HLS value to a RGB value.

    :param bool normalised: If *normalised* is True, the input HLS triplet
        should be in the range 0-1; otherwise, H in the range 0-360 and LS
        in the range 0-100.

    :return: the RGB triplet. The output
        triplet is in the range 0-1 whether the input is normalised or not.

    .. doctest::

        >>> from colormap.colors import hls2rgb
        >>> hls2rgb(360, 50, 60, normalised=False)  # doctest: +SKIP
        (0.8, 0.2, 0.2)


    .. seealso:: :func:`hex2web`, :func:`web2hex`, :func:`hex2rgb`
        :func:`rgb2hex`, :func:`rgb2hsv`, :func:`hsv2rgb`, :func:`rgb2hls`,

    """
    if normalised:
        upper = 1
    else:
        upper = 100
    if normalised:
        uppera = 1
    else:
        uppera = 360
    check_range(h, 0, uppera)
    check_range(s, 0, upper)
    check_range(l, 0, upper)
    if normalised == False:
        h, l, s = _normalise(h, l, s, mode="hls")
    return colorsys.hls_to_rgb(h, l, s)


def hex2dec(data):
    """convert hexadecimal string (data) into a float in the [0-65536] inclusive range"""
    if data[0] == '#':
        data.replace('#', '')
    return int(data, 16)/255.

def rgb2yuv(r, g, b):
    """Convert RGB triplet into YUV

    :return: YUV triplet with values between 0 and 1

    `YUV wikipedia <http://en.wikipedia.org/wiki/YUV>`_

    .. warning:: expected input must be between 0 and 1
    .. note:: the constants referenc used is  Rec. 601
    """
    check_range(r, 0, 1)
    check_range(g, 0, 1)
    check_range(b, 0, 1)

    #y = int(0.299 * r + 0.587 * g + 0.114 * b)
    #u = int(-0.14713 * r + -0.28886 * g + 0.436 * b)
    #v = int(0.615 * r + -0.51499 * g + -0.10001 * b)

    y = 0.299 * r + 0.587 * g + 0.114 * b
    u = -32591.0/221500.0 * r + -63983.0/221500.0 * g + 0.436 * b
    v = 0.615 * r + -72201./140200 * g + -7011/70100. * b
    return (y, u, v)


def yuv2rgb(y, u, v):
    """Convert YUV triplet into RGB

    `YUV <http://en.wikipedia.org/wiki/YUV>`_

    .. warning:: expected input must be between 0 and 255 (not normalised)

    """
    check_range(y, 0,1)
    check_range(u, 0, 1)
    check_range(v, 0, 1)
    A, B, C, D = 701.0/615.0, 25251.0/63983.0, 209599.0/361005.0, 443.0/218.0
    r = y + A * v
    g = y - B * u - C * v
    b = y + D * u
    return (r, g, b)


def rgb2yuv_int(r, g, b):
    """Convert RGB triplet into YUV

    `YUV wikipedia <http://en.wikipedia.org/wiki/YUV>`_

    .. warning:: expected input must be between 0 and 255 (not normalised)

    """
    check_range(r, 0, 255)
    check_range(g, 0, 255)
    check_range(b, 0, 255)

    y = int(0.299 * r + 0.587 * g + 0.114 * b)
    u = int(-32591.0/221500.0 * r + -63983.0/221500.0 * g + 0.436 * b)
    v = int(0.615 * r + -72201./140200 * g + -7011/70100. * b)

    return (y, u, v)


def yuv2rgb_int(y, u, v):
    """Convert YUV triplet into RGB

    `YUV <http://en.wikipedia.org/wiki/YUV>`_

    .. warning:: expected input must be between 0 and 255 (not normalised)

    """
    check_range(y, 0, 255)
    check_range(u, 0, 255)
    check_range(v, 0, 255)
    r = int(y + 1.13983 * v)
    g = int(y - 0.39465 * u - 0.58060 * v)
    b = int(y + 2.03211 * u)
    return (r, g, b)


def _denormalise(r, g, b, mode="rgb"):
    check_param_in_list(mode, ["rgb", "hls", "hsv"])
    if mode == "rgb":
        return r*255., g*255., b*255.
    elif mode in ["hls", "hsv"]:
        return r*360., g*100., b*100.


def _normalise(r, g, b, mode="rgb"):
    check_param_in_list(mode, ["rgb", "hls", "hsv"])
    if mode == "rgb":
        return r/255., g/255., b/255.
    elif mode in ["hls", "hsv"]:
        return r/360., g/100., b/100.


def to_intensity(n):
    """Return intensity

    :param n: value between 0 and 1
    :return: value between 0 and 255; round(n*127.5+127.5)
    """
    check_range(n, 0, 1)
    return int(round(n * 127.5 + 127.5))


class HEX(object):
    """Class to check the validity of an hexadecimal string and get standard string

    By standard, we mean #FFFFFF (6 digits)

    ::

        >>> h = HEX()
        >>> h.is_valid_hex_color("#FFFF00")
        True

    """
    def __init__(self):
        pass

    def is_valid_hex_color(self, value, verbose=True):
        """Return True is the string can be interpreted as hexadecimal color

        Valid formats are

         * #FFF
         * #0000FF
         * 0x0000FF
         * 0xFA1
        """
        try:
            self.get_standard_hex_color(value)
            return True
        except Exception as err:
            if verbose:
                print(err)
            return False

    def get_standard_hex_color(self, value):
        """Return standard hexadecimal color

        By standard, we mean a string that starts with # sign followed by 6
        character, e.g. #AABBFF
        """
        if isinstance(value, str)==False:
            raise TypeError("value must be a string")
        if len(value) <= 3:
            raise ValueError("input string must be of type 0xFFF, 0xFFFFFF or #FFF or #FFFFFF")

        if value.startswith("0x") or value.startswith("0X"):
            value =  value[2:]
        elif value.startswith("#"):
            value = value[1:]
        else:
            raise ValueError("hexa string must start with a '#' sign or '0x' string")
        value = value.upper()
        # Now, we have either FFFFFF or FFF
        # now check the length
        for x in value:
            if x not in "0123456789ABCDEF":
                raise ValueError("Found invalid hexa character {0}".format(x))

        if len(value) == 6 or len(value) == 8:
            value  = "#" + value[0:6]
        elif len(value) == 3:
            value = "#" + value[0]*2 + value[1]*2 + value[2]*2
        else:
            raise ValueError("hexa string should be 3, 6 or 8 digits. if 8 digits, last 2 are ignored")
        return value


class Color(HEX):
    """Class to ease manipulation and conversion between color codes

    You can create an instance in many differen ways. You can either use a
    human-readable name as long as it is part of the
    `XFree86 list <http://en.wikipedia.org/wiki/X11_color_names>`_
    You can also provide a hexadecimal string (either 3 or 6 digits). You can
    use triplets of values corresponding to the RGB, HSV or HLS conventions.

    Here are some examples:

    .. doctest::

        from colormap import Color

        Color("red")           # human XFree86 compatible representation
        Color("#f00")          # standard 3 hex digits
        Color("#ff0000")       # standard 6 hex digits
        Color(hsv=(0,1,0.5))
        Color(hls=(0, 1, 0.5)) # HLS triplet
        Color(rgb=(1, 0, 0))   # RGB triplet
        Color(Color("red"))    # using an instance of :class:`Color`

    Note that the RGB, HLS and HSV triplets use normalised values. If you need
    to normalise the triplet, you can use :mod:`colormap.colors._normalise` that
    provides a function to normalise RGB, HLS and HSV triplets::

        colors._normalise(*(255, 255, 0), mode="rgb")
        colors._normalise(*(360, 50, 100), mode="hls")

    If you provide a string, it has to be a valid string from XFree86.
    In addition to the official names, the lower case names are valid. Besides,
    there are names with spaces. The equivalent names without space are also
    valid. Therefore the name "Spring Green", which is an official name can be
    provided as "Spring Green", "spring green", "springgreen" or "SpringGreen".

    """
    # Get official color names
    colors = XFree86_colors.copy()
    # add color names without spaces
    aliases = dict([(x.replace(" ", ""),x) for x in colors.keys() if " " in x])
    # add color names without spaces in lower cases
    aliases.update([(x.replace(" ", "").lower(),x) for x in colors.keys() if " " in x])
    # add color names in lower case
    aliases.update(dict([(x.lower(),x) for x in colors.keys()]))
    aliases.update(dict([(x,x) for x in colors.keys()]))

    # keep track of all possible names
    color_names = sorted(list(set(list(colors.keys()) +list( aliases.keys()))))

    def __init__(self, name=None, rgb=None, hls=None, hsv=None):
        super(Color, self).__init__()
        self._name = None
        self._mode = None
        self._rgb = None

        # Does the user provided the name argument (first one) as a string ?
        if isinstance(name, str):
            # if so, it can be a valid human name (e.g., red) or an hex
            # assuming that valid hexadecimal starts with # or 0x,
            # if we can interpret the string as an hexadecimal, we are done
            if self.is_valid_hex_color(name, verbose=False):
                self.hex = name
            else:
                # if not, then, the user probably provided a valid color name
                # the property will check the validity.
                self.name = name[:]
                #all other input parameters are ignored
        elif name == None:
            if rgb:
                self.rgb = rgb
            elif hls:
                self.hls = hls
            elif hsv:
                self.hsv = hsv
            else:
                raise ValueError("You must set one of the parameter")
        elif isinstance(name, Color):
            self.rgb = name.rgb
        else:
            raise ValueError("name parameter must be a string")

    def _get_name(self):
        return self._name
    def _set_name(self, name):
        check_param_in_list(name, self.color_names)
        name = self.aliases[name]
        self._name = name
        # set hex and rgb at the same time based on the name
        self.hex = self.colors[name]
    name = property(_get_name, _set_name)
    color = property(_get_name, _set_name)

    def _get_hex(self):
        return self._hex
    def _set_hex(self, value):
        # hex is an approximation made of 255 bits so do not define rgb here
        if self.is_valid_hex_color(value):
            value = self.get_standard_hex_color(value)
            self._hex = value
            if self._hex in self.colors.values():
                self._name = swapdict(self.colors, check_ambiguity=False)[self._hex]
            else:
                self._name = "undefined"
            self._rgb = hex2rgb(self._hex, normalise=True)
        else:
            # just to warn the user
            self.get_standard_hex_color(value)
    hex = property(_get_hex, _set_hex, 
            doc="getter/setter the hexadecimal value.")

    def _get_rgb(self):
        return self._rgb
    def _set_rgb(self, value):
        # set name, hex and rgb
        self.hex = rgb2hex(*value , normalised=True)
        # must reset rgb with its real value (set_hex may round the rgb)
        # in _set_hex
        self._rgb = value
    rgb = property(_get_rgb, _set_rgb,
            doc="getter/setter the RGB values (3-length tuple)")

    def _get_hsv(self):
        hsv = rgb2hsv(*self.rgb)
        return hsv
    def _set_hsv(self, value):
        # TODO: value must be normalised
        self.rgb = hsv2rgb(*value)
    hsv = property(_get_hsv, _set_hsv,
            doc="getter/setter the HSV values (3-length tuple)")

    def _get_hls(self):
        hls = rgb2hls(*self.rgb)
        return hls
    def _set_hls(self, value):
        #hls = _normalise(*value, mode="hls")
        #else:
        hls = value
        self.rgb = hls2rgb(*hls)
    hls = property(_get_hls, _set_hls,
            doc="getter/setter the HLS values (3-length tuple)")

    def _get_lightness(self):
        return self.hls[1]
    def _set_lightness(self, lightness):
        h, l, s = self.hls
        self.hls = (h, lightness, s)
    lightness = property(_get_lightness, _set_lightness,
            doc="getter/setter the lightness in the HLS triplet")

    def _get_saturation_hls(self):
        return self.hls[2]
    def _set_saturation_hls(self, saturation):
        h, l, s = self.hls
        self.hls = (h, l, saturation)
    saturation_hls = property(_get_saturation_hls, _set_saturation_hls,
            doc="getter/setter the saturation in the HLS triplet")

    def _get_hue(self):
        return self.hls[0]
    def _set_hue(self, hue):
        h, l, s = self.hls
        self.hls = (hue, l, s)
    hue = property(_get_hue, _set_hue,
            doc="getter/setter the saturation in the HLS triplet")

    def _get_red(self):
        return self.rgb[0]
    def _set_red(self, red):
        r, g, b = self.rgb
        self.rgb = (red,g,b)
    red = property(_get_red, _set_red,
            doc="getter/setter for the red color in RGB triplet")

    def _get_green(self):
        return self.rgb[1]
    def _set_green(self, green):
        r, g, b = self.rgb
        self.rgb = (r, green, b)
    green = property(_get_green, _set_green,
            doc="getter/setter for the green color in RGB triplet")

    def _get_blue(self):
        return self.rgb[2]
    def _set_blue(self, blue):
        r, g, b = self.rgb
        self.rgb = (r, g, blue)
    blue = property(_get_blue, _set_blue,
            doc="getter/setter for the blue color in RGB triplet")

    def _get_value(self):
        return self.hls[0]
    def _set_value(self, value):
        h, s, v = self.hsv
        self.hsv = (h, s, value)
    value = property(_get_value, _set_value,
            doc="getter/setter the value in the HSV triplet")

    def _get_yiq(self):
        return colorsys.rgb_to_yiq(*self.rgb)
    yiq = property(_get_yiq, doc="Getter for the YIQ triplet")

    def __str__(self):
        txt = 'Color {0}\n'.format(self.name)
        txt+= '  hexa code: {0}\n'.format(self.hex)
        txt+= '  RGB code: {0}\n'.format(self.rgb)
        txt+= '  RGB code (un-normalised): {0}\n\n'.format([x*255 for x in self.rgb])
        txt+= '  HSV code: {0}\n'.format(self.hsv)
        txt+= '  HSV code: (un-normalised) {0} {1} {2}\n\n'.format(self.hsv[0]*360, self.hsv[1]*100, self.hsv[2]*100)
        txt+= '  HLS code: {0}\n'.format(self.hls)
        txt+= '  HLS code: (un-normalised) {0} {1} {2}\n\n'.format(self.hls[0]*360, self.hls[1]*100, self.hls[2]*100)
        return txt


class Colormap(object):
    """Class to create matplotlib colormap

    This example show how to get the pre-defined colormap called *heat*

    .. plot::
        :include-source:


        from pylab import *
        from colormap.colors import Colormap

        c = Colormap()
        cmap = c.get_cmap_heat()
        c.test_colormap(cmap)

    You may be more interested in building your own colormap::

        # design your own colormap
        d = {'blue': [0,0,0,1,1,1,0],
                'green':[0,1,1,1,0,0,0],
                'red':  [1,1,0,0,0,1,1]}
        cmap = c.cmap(d, reverse=False)

        # see the results
        c.test_colormap(cmap)

    If you want a simple linear colormap, you can use the example above,
    or use the :meth:`cmap_linear`. For instance for a diverging colormap
    from red to green (with with color in between)::

        cmap = c.cmap_linear("red", "white", "green")
        c.test_colormap(cmap)

    Even simpler, you can use a bicolor colormap :meth:`cmap_bicolor`. For instance 
    for a red to green colormap::

        cmap = c.cmap_bicolor("red", "green")
        c.test_colormap(cmap)

    From matplotlib documentation, colormaps falls into 4 categories:

    #. Sequential schemes for unipolar data that progresses from low to high
    #. Diverging schemes for bipolar data that emphasizes positive or
       negative deviations from acentral value
    #. Cyclic schemes  meant for plotting values that wrap around at the
       endpoints, such as phase angle, wind direction, or time of day
    #. Qualitative schemes for nominal data that has no inherent ordering,
       where color is used only to distinguish categories


    :references: matplotlib documentation and examples
        http://matplotlib.org/examples/color/colormaps_reference.html
    """
    def _get_colormap_mpl(self):
        try:
            from matplotlib.pyplot import colormaps as _cmaps
            return _cmaps()
        except:
            return []
    colormaps = property(_get_colormap_mpl)

    def _get_sequentials(self):
        return ['Blues', 'BuGn', 'BuPu', 'GnBu', 'Greens', 'Greys', 'OrRd',
                'Oranges', 'PuBu', 'PuBuGn', 'PuRd', 'Purples', 'RdPu',
                'Reds', 'YlGn', 'YlGnBu', 'YlOrBr', 'YlOrRd']
    sequentials = property(_get_sequentials)

    def _get_sequentials2(self):
        return ['afmhot', 'autumn', 'bone', 'cool', 'copper',
               'gist_heat', 'gray', 'hot', 'pink',
               'spring', 'summer', 'winter']
    sequentials2 = property(_get_sequentials2)

    def _get_diverging(self):
         return ['BrBG',  'PRGn',  'PiYG', 'PuOr', 'RdBu', 'RdGy', 'RdYlBu',
                 'RdYlGn', 'Spectral', 'bwr', 'coolwarm', 'seismic']
    diverging = property(_get_diverging)

    def _get_diverging_black(self):
         return ['red_black_sky', 'red_black_blue', 'red_black_green', 'yellow_black_blue',
                 'yellow_black_sky', 'red_black_orange', 'pink_black_green(w3c)'
                 ]
    diverging_black = property(_get_diverging_black)

    def _get_qualitative(self):
        return ['Accent', 'Dark2', 'Paired', 'Pastel1', 'Pastel2',
                'Set1', 'Set2', 'Set3']
    qualitative = property(_get_qualitative)

    def _get_misc(self):
        return ['gist_earth', 'terrain', 'ocean', 'gist_stern',
                'brg', 'CMRmap', 'cubehelix', 'gnuplot', 'gnuplot2', 'gist_ncar',
                'nipy_spectral', 'jet', 'rainbow', 'gist_rainbow', 'hsv', 'flag', 'prism']
    misc = property(_get_misc)

    def plot_rgb_from_hex_list(self, cols):
        """This functions takes a list of hexadecimal values and plots
        the RGB curves. This can be handy to figure out the RGB functions
        to be used in the :meth:`get_cmap`.

        .. plot::
            :include-source:
            :width: 60%

            from colormap.colors import Colormap
            c = Colormap()
            t = ['#FF0000FF', '#FF4D00FF', '#FF9900FF', '#FFE500FF',
                 '#CCFF00FF', '#80FF00FF', '#33FF00FF', '#00FF19FF',
                 '#00FF66FF', '#00FFB2FF', '#00FFFFFF', '#00B3FFFF',
                 '#0066FFFF', '#001AFFFF', '#3300FFFF', '#7F00FFFF',
                 '#CC00FFFF','#FF00E6FF','#FF0099FF', '#FF004DFF']
            c.plot_rgb_from_hex_list(t)

        """
        import pylab
        red = [hex2rgb(x)[0]/255. for x in cols]
        blue = [hex2rgb(x)[2]/255. for x in cols]
        green = [hex2rgb(x)[1]/255. for x in cols]
        x = pylab.linspace(0, 1, len(cols))
        pylab.clf()
        pylab.plot(x, red, 'ro-', alpha=0.5)
        pylab.plot(x, green, 'gs-', alpha=0.5, markersize=15)
        pylab.plot(x, blue, 'bx-', alpha=0.5, markersize=15)
        pylab.ylim([-0.1, 1.1])

    def cmap_bicolor(self, color1, color2, reverse=False, N=256):
        """Provide 3 colors in format accepted by :class:`Color`

        ::

            >>> red = Color('red')
            >>> white = Color('white')
            >>> cmap = cmap_bicolor(red, white)

        """
        c1 = Color(color1)
        c2 = Color(color2)
        dico = {'red': [c1.red, c2.red],
                'green':[c1.green, c2.green],
                'blue':[c1.blue, c2.blue]}
        return self.cmap(dico, reverse=reverse, N=N)

    def cmap_linear(self, color1, color2, color3, reverse=False, N=256):
        """Provide 3 colors in format accepted by :class:`Color`

        ::

            red = Color('red')
            cmap = cmap_linear(red, 'white', '#0000FF')

        """
        c1 = Color(color1)
        c2 = Color(color2)
        c3 = Color(color3)
        dico = {'red': [c1.red, c2.red, c3.red],
                'green':[c1.green, c2.green, c3.green],
                'blue':[c1.blue, c2.blue, c3.blue]}

        return self.cmap(dico, reverse=reverse, N=N)

    def cmap(self, colors=None, reverse=False, N=256):
        """Return a colormap object to be used within matplotlib

        :param dict colors: a dictionary that defines the RGB colors to be
            used in the colormap. See :meth:`get_cmap_heat` for an example.
        :param bool reverse: reverse the colormap is  set to True (defaults to False)
        :param int N: Defaults to 50

        """
        # matplotlib colormaps
        if colors in self.colormaps:
            if reverse and colors.endswith("_r") is False:
                colors += "_r"
            from matplotlib.cm import get_cmap
            return get_cmap(colors)
        # custom ones
        elif colors in self.diverging_black:
            c1, c2, c3 = colors.split("_")
            # special case of sky, which does not exists
            c3 = c3.replace("sky", "deep sky blue")
            return self.cmap_linear(c1, c2, c3)
        elif colors == 'heat':
            return self.get_cmap_heat()
        elif colors == 'heat_r':
            return self.get_cmap_heat_r()


        # Keep these dependencies inside the function to allow
        # installation of colormap without those dependencies
        # FIXME remove numpy dependencies
        import numpy as np
        # extracted from R, heat.colors(20)

        if reverse:
            for k in colors.keys():
                colors[k].reverse()

        # If index not given, RGB colors are evenly-spaced in colormap.
        index = np.linspace(0, 1, len(colors['red']))

        # Adapt color_data to the form expected by LinearSegmentedColormap.
        color_data = dict((key, [(x, y, y) for x, y in zip(index, value)])
                 for key, value in list(colors.items()))

        import matplotlib
        f = matplotlib.colors.LinearSegmentedColormap
        m = f('my_color_map', color_data, N)
        return m


    def get_cmap_heat(self):
        """Return a heat colormap matplotlib-compatible colormap

        This heat colormap should be equivalent to heat.colors() in R.

        ::

            >>> from colormap.colors import Colormap
            >>> cmap = Colormap.get_cmap_heat()

        You can generate the colormap based solely on this information for the RGB
        functions along::

            d=  {   'blue':[0,0,0,0,1],
                    'green':[0,.35,.7,1,1],
                    'red':[1,1,1,1,1]}
            cmap = Colormap.get_cmap(d)

        """
        return self.cmap(
                {   'blue':[0, 0, 0, 0, 1],
                    'green':[0, .35, .7, 1, 1],
                    'red':[1, 1, 1, 1, 1]}, reverse=False)

    def get_cmap_heat_r(self):
        """Return a heat colormap matplotlib-compatible colormap

        Same as :meth:`get_cmap_heat` but reversed
        """
        return self.cmap(
                {   'blue':[0, 0, 0, 0, 1],
                    'green':[0, .35, .7, 1, 1],
                    'red':[1, 1, 1, 1, 1]}, reverse=True)

    def get_cmap_rainbow(self):
        """colormap similar to rainbow colormap from R

        .. note:: The red is actually appearing on both sides... Yet
            this looks like what is coded in R 3.0.1

        """
        return self.cmap(
                {   'blue': [0, 0, 0, 1, 1, 1, 0],
                    'green':[0, 1, 1, 1, 0, 0, 0],
                    'red':  [1, 1, 0, 0, 0, 1, 1]}, reverse=False)


    def get_cmap_red_green(self):
        return self.cmap(
                {   'green': [0, 0.4, 0.6, .75, .8, .9, 1, .9, .8, .6],
                    'blue' : [0, .4, .6, .75, .8, .7, .6, .35, .17, .1],
                    'red':   [1, 1, 1, 1, 1, .9, .8, .6, .3, .1]}, reverse=True)

    def test_colormap(self, cmap=None):
        """plot one colormap for testing

        By default, test the :meth:`get_cmap_heat`

        """
        if cmap is None:
            cmap = self.get_cmap_heat()
        import numpy as np
        from pylab import clf, pcolor, colorbar, show, linspace, axis
        A, B = np.meshgrid(linspace(0, 10, 100), linspace(0, 10, 100))
        clf()
        pcolor((A-5)**2+(B-5)**2, cmap=cmap)
        colorbar()
        show()
        axis('off')

    def plot_colormap(self, cmap_list=None):
        """cmap_list list of valid cmap or name of a set (sequential,
        diverging,)

        if none, plot all known colors

        .. .. plot::
        ..    :width:80%
        ..    :include-source:

        ..    from colormap import Colormap
        ..    c = Colormap()
        ..    c.plot_colormap('sequential')


        """
        from pylab import subplots

        if isinstance(cmap_list, str):
            if cmap_list in ['sequentials','sequentials2','qualitative',
                             'misc','diverging', 'diverging_black']:
                cmap_list = getattr(self, cmap_list)
            else:
                cmap_list = [cmap_list]
        if isinstance(cmap_list, list) is not True:
            raise TypeError("""input must be a list of srtings or a single string. Each string should be found. For a user-defined cmap, use test_colormap""")
        for this in cmap_list:
            if this not in self.colormaps and this not in self.diverging_black:
                raise ValueError("unknown colormap name. Please check valid names in colormaps attribute")

        nrows = len(cmap_list)

        gradient = [x/255. for x in range(0,256)]
        gradient = [gradient, gradient]
        #np.vstack((gradient, gradient))

        fig, axes = subplots(nrows=nrows)
        fig.subplots_adjust(top=0.95, bottom=0.05, left=0.05, right=0.8)

        for ax, name in zip(axes, cmap_list):
            ax.imshow(gradient, aspect='auto', cmap=self.cmap(name))
            pos = list(ax.get_position().bounds)
            x_text = pos[2] + 0.08
            y_text = pos[1] + pos[3]/2.
            fig.text(x_text, y_text, name, va='center', ha='left', fontsize=10)

        # Turn off *all* ticks & spines, not just the ones with colormaps.
        for ax in axes:
            ax.set_axis_off()
