Source code for rootpy.plotting.base

"""
This module contains base classes defining core funcionality
"""
from __future__ import absolute_import

from functools import wraps
import warnings
import sys

from .. import asrootpy, ROOT
from ..decorators import chainable
from ..memory.keepalive import keepalive
from ..extern.six import string_types

__all__ = [
    'dim',
    'Plottable',
]


def dim(thing):
    if hasattr(thing.__class__, 'DIM'):
        return thing.__class__.DIM
    elif hasattr(thing, '__dim__'):
        return thing.__dim__()
    elif hasattr(thing, 'GetDimension'):
        return thing.GetDimension()
    else:
        raise TypeError(
            "Unable to determine dimensionality of "
            "object of type {0}".format(type(thing)))


class Plottable(object):
    """
    This is a mixin to provide additional attributes for plottable classes
    and to override ROOT TAttXXX and Draw methods.
    """
    EXTRA_ATTRS = {
        'norm': None,
        'drawstyle': '',
        'legendstyle': 'P',
        'integermode': False,
        'visible': True,
        'inlegend': True,
        }

    EXTRA_ATTRS_DEPRECATED = {
        'format': 'drawstyle',
        'intmode': 'integermode',
        }

    EXTRA_SETTERS = [
        'color',
        ]

    # TODO: respect current TStyle
    DEFAULT_DECOR = {
        'markerstyle': 'circle',
        'markercolor': 'black',
        'markersize': 1,
        'fillcolor': 'white',
        'fillstyle': 'hollow',
        'linecolor': 'black',
        'linestyle': 'solid',
        'linewidth': 1,
        }

    @classmethod
    def _get_attr_depr(cls, depattr, newattr):
        def f(self):
            warnings.warn(
                "`{0}` is deprecated and will be removed in "
                "future versions. Use `{1}` instead".format(
                    depattr, newattr),
                DeprecationWarning)
            return getattr(self, newattr)
        return f

    @classmethod
    def _set_attr_depr(cls, depattr, newattr):
        def f(self, value):
            warnings.warn(
                "`{0}` is deprecated and will be removed in "
                "future versions. Use `{1}` instead".format(
                    depattr, newattr),
                DeprecationWarning)
            setattr(self, newattr, value)
        return f

    def _post_init(self, **kwargs):
        self._clone_post_init(**kwargs)

    def _clone_post_init(self, obj=None, **kwargs):
        """
        obj must be another Plottable instance. obj is used by Clone to properly
        transfer all attributes onto this object.
        """
        # Initialize the extra attributes
        if obj is None or obj is self:
            # We must be asrootpy-ing a ROOT object
            # or freshly init-ing a rootpy object
            for attr, value in Plottable.EXTRA_ATTRS.items():
                # Use the default value
                setattr(self, attr, value)
        else:
            for attr, value in Plottable.EXTRA_ATTRS.items():
                setattr(self, attr, getattr(obj, attr))

        # Create aliases from deprecated to current attributes
        for depattr, newattr in Plottable.EXTRA_ATTRS_DEPRECATED.items():
            setattr(Plottable, depattr,
                    property(
                        fget=Plottable._get_attr_depr(depattr, newattr),
                        fset=Plottable._set_attr_depr(depattr, newattr)))

        if obj is None or obj is self:
            # We must be asrootpy-ing a ROOT object
            # or freshly init-ing a rootpy object
            # Initialize style attrs to style of TObject
            if isinstance(self, ROOT.TAttLine):
                self._linecolor = Color(ROOT.TAttLine.GetLineColor(self))
                self._linestyle = LineStyle(ROOT.TAttLine.GetLineStyle(self))
                self._linewidth = ROOT.TAttLine.GetLineWidth(self)
            else:  # HistStack
                self._linecolor = Color(Plottable.DEFAULT_DECOR['linecolor'])
                self._linestyle = LineStyle(Plottable.DEFAULT_DECOR['linestyle'])
                self._linewidth = Plottable.DEFAULT_DECOR['linewidth']

            if isinstance(self, ROOT.TAttFill):
                self._fillcolor = Color(ROOT.TAttFill.GetFillColor(self))
                self._fillstyle = FillStyle(ROOT.TAttFill.GetFillStyle(self))
            else:  # HistStack
                self._fillcolor = Color(Plottable.DEFAULT_DECOR['fillcolor'])
                self._fillstyle = FillStyle(Plottable.DEFAULT_DECOR['fillstyle'])

            if isinstance(self, ROOT.TAttMarker):
                self._markercolor = Color(ROOT.TAttMarker.GetMarkerColor(self))
                self._markerstyle = MarkerStyle(ROOT.TAttMarker.GetMarkerStyle(self))
                self._markersize = ROOT.TAttMarker.GetMarkerSize(self)
            else:  # HistStack
                self._markercolor = Color(Plottable.DEFAULT_DECOR['markercolor'])
                self._markerstyle = MarkerStyle(Plottable.DEFAULT_DECOR['markerstyle'])
                self._markersize = Plottable.DEFAULT_DECOR['markersize']

            if obj is None:
                # Populate defaults if we are not asrootpy-ing existing object
                decor = dict(**Plottable.DEFAULT_DECOR)
                decor.update(Plottable.EXTRA_ATTRS)
                if 'color' in kwargs:
                    decor.pop('linecolor', None)
                    decor.pop('fillcolor', None)
                    decor.pop('markercolor', None)
                decor.update(kwargs)
                self.decorate(**decor)

        else:
            # Initialize style attrs to style of the other object
            if isinstance(obj, ROOT.TAttLine):
                self.SetLineColor(obj.GetLineColor())
                self.SetLineStyle(obj.GetLineStyle())
                self.SetLineWidth(obj.GetLineWidth())
            if isinstance(obj, ROOT.TAttFill):
                self.SetFillColor(obj.GetFillColor())
                self.SetFillStyle(obj.GetFillStyle())
            if isinstance(obj, ROOT.TAttMarker):
                self.SetMarkerColor(obj.GetMarkerColor())
                self.SetMarkerStyle(obj.GetMarkerStyle())
                self.SetMarkerSize(obj.GetMarkerSize())
            if kwargs:
                self.decorate(**kwargs)

    #TODO: @chainable
    def decorate(self, other=None, **kwargs):
        """
        Apply style options to a Plottable object.

        Returns a reference to self.
        """
        if 'color' in kwargs:
            incompatible = []
            for othercolor in ('linecolor', 'fillcolor', 'markercolor'):
                if othercolor in kwargs:
                    incompatible.append(othercolor)
            if incompatible:
                raise ValueError(
                    "Setting both the `color` and the `{0}` attribute{1} "
                    "is ambiguous. Please set only one.".format(
                        ', '.join(incompatible),
                        's' if len(incompatible) != 1 else ''))
        if other is not None:
            decor = other.decorators
            if 'color' in kwargs:
                decor.pop('linecolor', None)
                decor.pop('fillcolor', None)
                decor.pop('markercolor', None)
            decor.update(kwargs)
            kwargs = decor
        for key, value in kwargs.items():
            if key in Plottable.EXTRA_ATTRS_DEPRECATED:
                newkey = Plottable.EXTRA_ATTRS_DEPRECATED[key]
                warnings.warn(
                    "`{0}` is deprecated and will be removed in "
                    "future versions. Use `{1}` instead".format(
                        key, newkey),
                    DeprecationWarning)
                key = newkey
            if key in Plottable.EXTRA_ATTRS:
                setattr(self, key, value)
            elif key == 'markerstyle':
                self.SetMarkerStyle(value)
            elif key == 'markercolor':
                self.SetMarkerColor(value)
            elif key == 'markersize':
                self.SetMarkerSize(value)
            elif key == 'fillcolor':
                self.SetFillColor(value)
            elif key == 'fillstyle':
                self.SetFillStyle(value)
            elif key == 'linecolor':
                self.SetLineColor(value)
            elif key == 'linestyle':
                self.SetLineStyle(value)
            elif key == 'linewidth':
                self.SetLineWidth(value)
            elif key == 'color':
                self.SetColor(value)
            else:
                raise AttributeError(
                    "unknown decoration attribute: `{0}`".format(key))
        return self

    @property
    def decorators(self):
        return {
            "norm": self.norm,
            "drawstyle": self.drawstyle,
            "legendstyle": self.legendstyle,
            "integermode": self.integermode,
            "visible": self.visible,
            "inlegend": self.inlegend,
            "markercolor": self.GetMarkerColor(),
            "markerstyle": self.GetMarkerStyle(),
            "markersize": self.GetMarkerSize(),
            "fillcolor": self.GetFillColor(),
            "fillstyle": self.GetFillStyle(),
            "linecolor": self.GetLineColor(),
            "linestyle": self.GetLineStyle(),
            "linewidth": self.GetLineWidth(),
        }

    def SetLineColor(self, color):
        """
        *color* may be any color understood by ROOT or matplotlib.

        For full documentation of accepted *color* arguments, see
        :class:`rootpy.plotting.style.Color`.
        """
        self._linecolor = Color(color)
        if isinstance(self, ROOT.TAttLine):
            ROOT.TAttLine.SetLineColor(self, self._linecolor('root'))

    def GetLineColor(self, mode=None):
        """
        *mode* may be 'root', 'mpl', or None to return the ROOT, matplotlib,
        or input value.
        """
        return self._linecolor(mode)

    @property
    def linecolor(self):
        return self.GetLineColor()

    @linecolor.setter
    def linecolor(self, color):
        self.SetLineColor(color)

    def SetLineStyle(self, style):
        """
        *style* may be any line style understood by ROOT or matplotlib.

        For full documentation of accepted *style* arguments, see
        :class:`rootpy.plotting.style.LineStyle`.
        """
        self._linestyle = LineStyle(style)
        if isinstance(self, ROOT.TAttLine):
            ROOT.TAttLine.SetLineStyle(self, self._linestyle('root'))

    def GetLineStyle(self, mode=None):
        """
        *mode* may be 'root', 'mpl', or None to return the ROOT, matplotlib,
        or input value.
        """
        return self._linestyle(mode)

    @property
    def linestyle(self):
        return self.GetLineStyle()

    @linestyle.setter
    def linestyle(self, style):
        self.SetLineStyle(style)

    def SetLineWidth(self, width):
        if isinstance(self, ROOT.TAttLine):
            ROOT.TAttLine.SetLineWidth(self, width)
        else:
            self._linewidth = width

    def GetLineWidth(self):
        if isinstance(self, ROOT.TAttLine):
            return ROOT.TAttLine.GetLineWidth(self)
        else:
            return self._linewidth

    @property
    def linewidth(self):
        return self.GetLineWidth()

    @linewidth.setter
    def linewidth(self, width):
        self.SetLineWidth(width)

    def SetFillColor(self, color):
        """
        *color* may be any color understood by ROOT or matplotlib.

        For full documentation of accepted *color* arguments, see
        :class:`rootpy.plotting.style.Color`.
        """
        self._fillcolor = Color(color)
        if isinstance(self, ROOT.TAttFill):
            ROOT.TAttFill.SetFillColor(self, self._fillcolor('root'))

    def GetFillColor(self, mode=None):
        """
        *mode* may be 'root', 'mpl', or None to return the ROOT, matplotlib,
        or input value.
        """
        return self._fillcolor(mode)

    @property
    def fillcolor(self):
        return self.GetFillColor()

    @fillcolor.setter
    def fillcolor(self, color):
        self.SetFillColor(color)

    def SetFillStyle(self, style):
        """
        *style* may be any fill style understood by ROOT or matplotlib.

        For full documentation of accepted *style* arguments, see
        :class:`rootpy.plotting.style.FillStyle`.
        """
        self._fillstyle = FillStyle(style)
        if isinstance(self, ROOT.TAttFill):
            ROOT.TAttFill.SetFillStyle(self, self._fillstyle('root'))

    def GetFillStyle(self, mode=None):
        """
        *mode* may be 'root', 'mpl', or None to return the ROOT, matplotlib,
        or input value.
        """
        return self._fillstyle(mode)

    @property
    def fillstyle(self):
        return self.GetFillStyle()

    @fillstyle.setter
    def fillstyle(self, style):
        self.SetFillStyle(style)

    def SetMarkerColor(self, color):
        """
        *color* may be any color understood by ROOT or matplotlib.

        For full documentation of accepted *color* arguments, see
        :class:`rootpy.plotting.style.Color`.
        """
        self._markercolor = Color(color)
        if isinstance(self, ROOT.TAttMarker):
            ROOT.TAttMarker.SetMarkerColor(self, self._markercolor('root'))

    def GetMarkerColor(self, mode=None):
        """
        *mode* may be 'root', 'mpl', or None to return the ROOT, matplotlib,
        or input value.
        """
        return self._markercolor(mode)

    @property
    def markercolor(self):
        return self.GetMarkerColor()

    @markercolor.setter
    def markercolor(self, color):
        self.SetMarkerColor(color)

    def SetMarkerStyle(self, style):
        """
        *style* may be any marker style understood by ROOT or matplotlib.

        For full documentation of accepted *style* arguments, see
        :class:`rootpy.plotting.style.MarkerStyle`.
        """
        self._markerstyle = MarkerStyle(style)
        if isinstance(self, ROOT.TAttMarker):
            ROOT.TAttMarker.SetMarkerStyle(self, self._markerstyle('root'))

    def GetMarkerStyle(self, mode=None):
        """
        *mode* may be 'root', 'mpl', or None to return the ROOT, matplotlib,
        or input value.
        """
        return self._markerstyle(mode)

    @property
    def markerstyle(self):
        return self.GetMarkerStyle()

    @markerstyle.setter
    def markerstyle(self, style):
        self.SetMarkerStyle(style)

    def SetMarkerSize(self, size):
        if isinstance(self, ROOT.TAttMarker):
            ROOT.TAttMarker.SetMarkerSize(self, size)
        else:
            self._markersize = size

    def GetMarkerSize(self):
        if isinstance(self, ROOT.TAttMarker):
            return ROOT.TAttMarker.GetMarkerSize(self)
        else:
            return self._markersize

    @property
    def markersize(self):
        return self.GetMarkerSize()

    @markersize.setter
    def markersize(self, size):
        self.SetMarkerSize(size)

    def SetColor(self, color):
        """
        *color* may be any color understood by ROOT or matplotlib.

        Set all color attributes with one method call.

        For full documentation of accepted *color* arguments, see
        :class:`rootpy.plotting.style.Color`.
        """
        self.SetFillColor(color)
        self.SetLineColor(color)
        self.SetMarkerColor(color)

    def GetColor(self):
        return self.GetMarkerColor(), self.GetLineColor(), self.GetFillColor()

    @property
    def color(self):
        return self.GetColor()

    @color.setter
    def color(self, color):
        self.SetColor(color)

    @property
    def xaxis(self):
        return asrootpy(self.GetXaxis())

    @property
    def yaxis(self):
        return asrootpy(self.GetYaxis())

    @property
    def zaxis(self):
        return asrootpy(self.GetZaxis())

    def Draw(self, *args, **kwargs):
        """
        Parameters
        ----------
        args : positional arguments
            Positional arguments are passed directly to ROOT's Draw
        kwargs : keyword arguments
            If keyword arguments are present, then a clone is drawn instead
            with DrawCopy, where the name, title, and style attributes are
            taken from ``kwargs``.

        Returns
        -------
        If ``kwargs`` is not empty and a clone is drawn, then the clone is
        returned, otherwise None is returned.
        """
        if kwargs:
            return self.DrawCopy(*args, **kwargs)

        pad = ROOT.gPad
        own_pad = False
        if not pad:
            # avoid circular import by delaying import until needed here
            from .canvas import Canvas
            pad = Canvas()
            own_pad = True
        if self.visible:
            if self.drawstyle:
                self.__class__.__bases__[-1].Draw(self,
                    " ".join((self.drawstyle, ) + args))
            else:
                self.__class__.__bases__[-1].Draw(self, " ".join(args))
            pad.Modified()
            pad.Update()
        if own_pad:
            keepalive(self, pad)

    def DrawCopy(self, *args, **kwargs):
        """
        Parameters
        ----------
        args : positional arguments
            Positional arguments are passed directly to ROOT's Draw
        kwargs : keyword arguments
            The name, title, and style attributes of the clone are
            taken from ``kwargs``.

        Returns
        -------
        The clone.
        """
        copy = self.Clone(**kwargs)
        copy.Draw(*args)
        return copy


class _StyleContainer(object):
    """
    Base class for grouping together an input style with ROOT and matplotlib
    styles.
    """
    def __init__(self, value, function):
        self._input = value
        self._root = function(value, 'root')
        try:
            self._mpl = function(value, 'mpl')
        except ValueError:
            self._mpl = self._root

    def __call__(self, output_type=None):
        if not output_type:
            output_type = 'input'
        return getattr(self, '_' + output_type)

    def __repr__(self):
        return str(self._input)


##############################
#### Markers #################

markerstyles_root2mpl = {
    1: '.',
    2: '+',
    3: '*',
    4: 'o',
    5: 'x',
    20: 'o',
    21: 's',
    22: '^',
    23: 'v',
    24: 'o',
    25: 's',
    26: '^',
    27: 'd',
    28: '+',
    29: '*',
    30: '*',
    31: '*',
    32: 'v',
    33: 'D',
    34: '+',
    }
for i in range(6, 20):
    markerstyles_root2mpl[i] = '.'

markerstyles_mpl2root = {
    '.': 1,
    ',': 1,
    'o': 4,
    'v': 23,
    '^': 22,
    '<': 23,
    '>': 22,
    '1': 23,
    '2': 22,
    '3': 23,
    '4': 22,
    's': 25,
    'p': 25,
    '*': 3,
    'h': 25,
    'H': 25,
    '+': 2,
    'x': 5,
    'D': 33,
    'd': 27,
    '|': 2,
    '_': 2,
    0: 1,  # TICKLEFT
    1: 1,  # TICKRIGHT
    2: 1,  # TICKUP
    3: 1,  # TICKDOWN
    4: 1,  # CARETLEFT
    5: 1,  # CARETRIGHT
    6: 1,  # CARETUP
    7: 1,  # CARETDOWN
    'None': '.',
    ' ': '.',
    '': '.',
    }

markerstyles_text2root = {
    "smalldot": 6,
    "mediumdot": 7,
    "largedot": 8,
    "dot": 9,
    "circle": 20,
    "square": 21,
    "triangle": 22,
    "triangleup": 22,
    "triangledown": 23,
    "opencircle": 24,
    "opensquare": 25,
    "opentriangle": 26,
    "opendiamond": 27,
    "diamond": 33,
    "opencross": 28,
    "cross": 34,
    "openstar": 29,
    "fullstar": 30,
    "star": 29,
    }


def convert_markerstyle(inputstyle, mode, inputmode=None):
    """
    Convert *inputstyle* to ROOT or matplotlib format.

    Output format is determined by *mode* ('root' or 'mpl').  The *inputstyle*
    may be a ROOT marker style, a matplotlib marker style, or a description
    such as 'star' or 'square'.
    """
    mode = mode.lower()
    if mode not in ('mpl', 'root'):
        raise ValueError("`{0}` is not valid `mode`".format(mode))
    if inputmode is None:
        if inputstyle in markerstyles_root2mpl:
            inputmode = 'root'
        elif inputstyle in markerstyles_mpl2root or '$' in str(inputstyle):
            inputmode = 'mpl'
        elif inputstyle in markerstyles_text2root:
            inputmode = 'root'
            inputstyle = markerstyles_text2root[inputstyle]
        else:
            raise ValueError(
                "`{0}` is not a valid `markerstyle`".format(inputstyle))
    if inputmode == 'root':
        if inputstyle not in markerstyles_root2mpl:
            raise ValueError(
                "`{0}` is not a valid ROOT `markerstyle`".format(
                    inputstyle))
        if mode == 'root':
            return inputstyle
        return markerstyles_root2mpl[inputstyle]
    else:
        if '$' in str(inputstyle):
            if mode == 'root':
                return 1
            else:
                return inputstyle
        if inputstyle not in markerstyles_mpl2root:
            raise ValueError(
                "`{0}` is not a valid matplotlib `markerstyle`".format(
                    inputstyle))
        if mode == 'mpl':
            return inputstyle
        return markerstyles_mpl2root[inputstyle]


class MarkerStyle(_StyleContainer):
    """
    Container for grouping together ROOT and matplotlib marker styles.

    The *style* argument to the constructor may be a ROOT marker style,
    a matplotlib marker style, or one of the following descriptions:
    """
    __doc__ = __doc__[:__doc__.rfind('\n') + 1]
    __doc__ += '\n'.join(["    '{0}'".format(x)
                          for x in markerstyles_text2root])
    if sys.version_info[0] < 3:
        del x
    __doc__ += """

    Examples
    --------

       >>> style = MarkerStyle('opentriangle')
       >>> style('root')
       26
       >>> style('mpl')
       '^'

    """
    def __init__(self, style):
        _StyleContainer.__init__(self, style, convert_markerstyle)


##############################
#### Lines ###################

linestyles_root2mpl = {
    1: 'solid',
    2: 'dashed',
    3: 'dotted',
    4: 'dashdot',
    5: 'dashdot',
    6: 'dashdot',
    7: 'dashed',
    8: 'dashdot',
    9: 'dashed',
    10: 'dashdot',
    }

linestyles_mpl2root = {
    'solid': 1,
    'dashed': 2,
    'dotted': 3,
    'dashdot': 4,
    }

linestyles_text2root = {
    'solid': 1,
    'dashed': 2,
    'dotted': 3,
    'dashdot': 4,
    'longdashdot': 5,
    'longdashdotdotdot': 6,
    'longdash': 7,
    'longdashdotdot': 8,
    'verylongdash': 9,
    'verylongdashdot': 10
    }


def convert_linestyle(inputstyle, mode, inputmode=None):
    """
    Convert *inputstyle* to ROOT or matplotlib format.

    Output format is determined by *mode* ('root' or 'mpl').  The *inputstyle*
    may be a ROOT line style, a matplotlib line style, or a description
    such as 'solid' or 'dotted'.
    """
    mode = mode.lower()
    if mode not in ('mpl', 'root'):
        raise ValueError(
            "`{0}` is not a valid `mode`".format(mode))
    try:
        inputstyle = int(inputstyle)
        if inputstyle < 1:
            inputstyle = 1
    except (TypeError, ValueError):
        pass
    if inputmode is None:
        if inputstyle in linestyles_root2mpl:
            inputmode = 'root'
        elif inputstyle in linestyles_mpl2root:
            inputmode = 'mpl'
        elif inputstyle in linestyles_text2root:
            inputmode = 'root'
            inputstyle = linestyles_text2root[inputstyle]
        else:
            raise ValueError(
                "`{0}` is not a valid `linestyle`".format(
                    inputstyle))
    if inputmode == 'root':
        if inputstyle not in linestyles_root2mpl:
            raise ValueError(
                "`{0}` is not a valid ROOT `linestyle`".format(
                    inputstyle))
        if mode == 'root':
            return inputstyle
        return linestyles_root2mpl[inputstyle]
    else:
        if inputstyle not in linestyles_mpl2root:
            raise ValueError(
                "`{0}` is not a valid matplotlib `linestyle`".format(
                    inputstyle))
        if mode == 'mpl':
            return inputstyle
        return linestyles_mpl2root[inputstyle]


class LineStyle(_StyleContainer):
    """
    Container for grouping together ROOT and matplotlib line styles.

    The *style* argument to the constructor may be a ROOT line style,
    a matplotlib line style, or one of the following descriptions:
    """
    __doc__ = __doc__[:__doc__.rfind('\n') + 1]
    __doc__ += '\n'.join(["    '{0}'".format(x)
                          for x in linestyles_text2root])
    if sys.version_info[0] < 3:
        del x
    __doc__ += """

    Examples
    --------

       >>> style = LineStyle('verylongdashdot')
       >>> style('root')
       10
       >>> style('mpl')
       'dashdot'

    """
    def __init__(self, style):
        _StyleContainer.__init__(self, style, convert_linestyle)


##############################
#### Fills ###################

fillstyles_root2mpl = {
    0: None,
    1001: None,
    3003: '.',
    3345: '\\',
    3354: '/',
    3006: '|',
    3007: '-',
    3011: '*',
    3012: 'o',
    3013: 'x',
    3019: 'O',
    }

fillstyles_mpl2root = {}
for key, value in fillstyles_root2mpl.items():
    fillstyles_mpl2root[value] = key
fillstyles_mpl2root[None] = 0

fillstyles_text2root = {
    'hollow': 0,
    'none': 0,
    'solid': 1001,
    }


def convert_fillstyle(inputstyle, mode, inputmode=None):
    """
    Convert *inputstyle* to ROOT or matplotlib format.

    Output format is determined by *mode* ('root' or 'mpl').  The *inputstyle*
    may be a ROOT fill style, a matplotlib hatch style, None, 'none', 'hollow',
    or 'solid'.
    """
    mode = mode.lower()
    if mode not in ('mpl', 'root'):
        raise ValueError("`{0}` is not a valid `mode`".format(mode))
    if inputmode is None:
        try:
            # inputstyle is a ROOT linestyle
            inputstyle = int(inputstyle)
            inputmode = 'root'
        except (TypeError, ValueError):
            if inputstyle is None:
                inputmode = 'mpl'
            elif inputstyle in fillstyles_text2root:
                inputmode = 'root'
                inputstyle = fillstyles_text2root[inputstyle]
            elif inputstyle[0] in fillstyles_mpl2root:
                inputmode = 'mpl'
            else:
                raise ValueError(
                    "`{0}` is not a valid `fillstyle`".format(inputstyle))
    if inputmode == 'root':
        if mode == 'root':
            return inputstyle
        if inputstyle in fillstyles_root2mpl:
            return fillstyles_root2mpl[inputstyle]
        raise ValueError(
            "`{0}` is not a valid `fillstyle`".format(inputstyle))
    else:
        if inputstyle is not None and inputstyle[0] not in fillstyles_mpl2root:
            raise ValueError(
                "`{0}` is not a valid matplotlib `fillstyle`".format(
                    inputstyle))
        if mode == 'mpl':
            return inputstyle
        if inputstyle is None:
            return fillstyles_mpl2root[inputstyle]
        return fillstyles_mpl2root[inputstyle[0]]


class FillStyle(_StyleContainer):
    """
    Container for grouping together ROOT and matplotlib fill styles.

    The *style* argument to the constructor may be a ROOT fill style,
    a matplotlib fill style, or one of the following descriptions:
    """
    __doc__ = __doc__[:__doc__.rfind('\n') + 1]
    __doc__ += '\n'.join(["    '{0}'".format(x)
                          for x in fillstyles_text2root])
    if sys.version_info[0] < 3:
        del x
    __doc__ += """

    For an input value of 'solid', the matplotlib hatch value will be set to
    None, which is the same value as for 'hollow'.  The root2matplotlib
    functions will all check the ROOT value to see whether to make the fill
    solid or hollow.

    Examples
    --------

       >>> style = FillStyle('hollow')
       >>> style('root')
       0
       >>> print style('mpl')
       None

    """
    def __init__(self, style):
        _StyleContainer.__init__(self, style, convert_fillstyle)


##############################
#### Colors ##################

_cnames = {
    'r'                    : '#FF0000', #@IgnorePep8
    'g'                    : '#00FF00',
    'b'                    : '#0000FF',
    'c'                    : '#00BFBF',
    'm'                    : '#BF00BF',
    'y'                    : '#BFBF00',
    'k'                    : '#000000',
    'w'                    : '#FFFFFF',
    'aliceblue'            : '#F0F8FF',
    'antiquewhite'         : '#FAEBD7',
    'aqua'                 : '#00FFFF',
    'aquamarine'           : '#7FFFD4',
    'azure'                : '#F0FFFF',
    'beige'                : '#F5F5DC',
    'bisque'               : '#FFE4C4',
    'black'                : '#000000',
    'blanchedalmond'       : '#FFEBCD',
    'blue'                 : '#0000FF',
    'blueviolet'           : '#8A2BE2',
    'brown'                : '#A52A2A',
    'burlywood'            : '#DEB887',
    'cadetblue'            : '#5F9EA0',
    'chartreuse'           : '#7FFF00',
    'chocolate'            : '#D2691E',
    'coral'                : '#FF7F50',
    'cornflowerblue'       : '#6495ED',
    'cornsilk'             : '#FFF8DC',
    'crimson'              : '#DC143C',
    'cyan'                 : '#00FFFF',
    'darkblue'             : '#00008B',
    'darkcyan'             : '#008B8B',
    'darkgoldenrod'        : '#B8860B',
    'darkgray'             : '#A9A9A9',
    'darkgreen'            : '#006400',
    'darkkhaki'            : '#BDB76B',
    'darkmagenta'          : '#8B008B',
    'darkolivegreen'       : '#556B2F',
    'darkorange'           : '#FF8C00',
    'darkorchid'           : '#9932CC',
    'darkred'              : '#8B0000',
    'darksalmon'           : '#E9967A',
    'darkseagreen'         : '#8FBC8F',
    'darkslateblue'        : '#483D8B',
    'darkslategray'        : '#2F4F4F',
    'darkturquoise'        : '#00CED1',
    'darkviolet'           : '#9400D3',
    'deeppink'             : '#FF1493',
    'deepskyblue'          : '#00BFFF',
    'dimgray'              : '#696969',
    'dodgerblue'           : '#1E90FF',
    'firebrick'            : '#B22222',
    'floralwhite'          : '#FFFAF0',
    'forestgreen'          : '#228B22',
    'fuchsia'              : '#FF00FF',
    'gainsboro'            : '#DCDCDC',
    'ghostwhite'           : '#F8F8FF',
    'gold'                 : '#FFD700',
    'goldenrod'            : '#DAA520',
    'gray'                 : '#808080',
    'green'                : '#008000',
    'greenyellow'          : '#ADFF2F',
    'honeydew'             : '#F0FFF0',
    'hotpink'              : '#FF69B4',
    'indianred'            : '#CD5C5C',
    'indigo'               : '#4B0082',
    'ivory'                : '#FFFFF0',
    'khaki'                : '#F0E68C',
    'lavender'             : '#E6E6FA',
    'lavenderblush'        : '#FFF0F5',
    'lawngreen'            : '#7CFC00',
    'lemonchiffon'         : '#FFFACD',
    'lightblue'            : '#ADD8E6',
    'lightcoral'           : '#F08080',
    'lightcyan'            : '#E0FFFF',
    'lightgoldenrodyellow' : '#FAFAD2',
    'lightgreen'           : '#90EE90',
    'lightgrey'            : '#D3D3D3',
    'lightpink'            : '#FFB6C1',
    'lightsalmon'          : '#FFA07A',
    'lightseagreen'        : '#20B2AA',
    'lightskyblue'         : '#87CEFA',
    'lightslategray'       : '#778899',
    'lightsteelblue'       : '#B0C4DE',
    'lightyellow'          : '#FFFFE0',
    'lime'                 : '#00FF00',
    'limegreen'            : '#32CD32',
    'linen'                : '#FAF0E6',
    'magenta'              : '#FF00FF',
    'maroon'               : '#800000',
    'mediumaquamarine'     : '#66CDAA',
    'mediumblue'           : '#0000CD',
    'mediumorchid'         : '#BA55D3',
    'mediumpurple'         : '#9370DB',
    'mediumseagreen'       : '#3CB371',
    'mediumslateblue'      : '#7B68EE',
    'mediumspringgreen'    : '#00FA9A',
    'mediumturquoise'      : '#48D1CC',
    'mediumvioletred'      : '#C71585',
    'midnightblue'         : '#191970',
    'mintcream'            : '#F5FFFA',
    'mistyrose'            : '#FFE4E1',
    'moccasin'             : '#FFE4B5',
    'navajowhite'          : '#FFDEAD',
    'navy'                 : '#000080',
    'oldlace'              : '#FDF5E6',
    'olive'                : '#808000',
    'olivedrab'            : '#6B8E23',
    'orange'               : '#FFA500',
    'orangered'            : '#FF4500',
    'orchid'               : '#DA70D6',
    'palegoldenrod'        : '#EEE8AA',
    'palegreen'            : '#98FB98',
    'palevioletred'        : '#AFEEEE',
    'papayawhip'           : '#FFEFD5',
    'peachpuff'            : '#FFDAB9',
    'peru'                 : '#CD853F',
    'pink'                 : '#FFC0CB',
    'plum'                 : '#DDA0DD',
    'powderblue'           : '#B0E0E6',
    'purple'               : '#800080',
    'red'                  : '#FF0000',
    'rosybrown'            : '#BC8F8F',
    'royalblue'            : '#4169E1',
    'saddlebrown'          : '#8B4513',
    'salmon'               : '#FA8072',
    'sandybrown'           : '#FAA460',
    'seagreen'             : '#2E8B57',
    'seashell'             : '#FFF5EE',
    'sienna'               : '#A0522D',
    'silver'               : '#C0C0C0',
    'skyblue'              : '#87CEEB',
    'slateblue'            : '#6A5ACD',
    'slategray'            : '#708090',
    'snow'                 : '#FFFAFA',
    'springgreen'          : '#00FF7F',
    'steelblue'            : '#4682B4',
    'tan'                  : '#D2B48C',
    'teal'                 : '#008080',
    'thistle'              : '#D8BFD8',
    'tomato'               : '#FF6347',
    'turquoise'            : '#40E0D0',
    'violet'               : '#EE82EE',
    'wheat'                : '#F5DEB3',
    'white'                : '#FFFFFF',
    'whitesmoke'           : '#F5F5F5',
    'yellow'               : '#FFFF00',
    'yellowgreen'          : '#9ACD32',
    }


def convert_color(color, mode):
    """
    Convert *color* to a TColor if *mode='root'* or to (r,g,b) if 'mpl'.

    The *color* argument can be a ROOT TColor or color index, an *RGB*
    or *RGBA* sequence or a string in any of several forms:

        1) a letter from the set 'rgbcmykw'
        2) a hex color string, like '#00FFFF'
        3) a standard name, like 'aqua'
        4) a float, like '0.4', indicating gray on a 0-1 scale

    if *arg* is *RGBA*, the transparency value will be ignored.
    """
    mode = mode.lower()
    if mode not in ('mpl', 'root'):
        raise ValueError(
            "`{0}` is not a valid `mode`".format(mode))
    try:
        # color is an r,g,b tuple
        color = tuple([float(x) for x in color[:3]])
        if max(color) > 1.:
            color = tuple([x / 255. for x in color])
        if mode == 'root':
            return ROOT.TColor.GetColor(*color)
        return color
    except (ValueError, TypeError):
        pass
    if isinstance(color, string_types):
        if color in _cnames:
            # color is a matplotlib letter or an html color name
            color = _cnames[color]
        if color[0] == '#':
            # color is a hex value
            color = color.lstrip('#')
            lv = len(color)
            color = tuple(int(color[i:i + lv // 3], 16)
                          for i in range(0, lv, lv // 3))
            if lv == 3:
                color = tuple(x * 16 + x for x in color)
            return convert_color(color, mode)
        # color is a shade of gray, i.e. '0.3'
        return convert_color((color, color, color), mode)
    try:
        # color is a TColor
        color = ROOT.TColor(color)
        color = color.GetRed(), color.GetGreen(), color.GetBlue()
        return convert_color(color, mode)
    except (TypeError, ReferenceError):
        pass
    try:
        # color is a ROOT color index
        if color < 0:
            color = 0
        color = ROOT.gROOT.GetColor(color)
        # Protect against the case a histogram with a custom color
        # is saved in a ROOT file
        if not color:
            # Just return black
            color = ROOT.gROOT.GetColor(1)
        color = color.GetRed(), color.GetGreen(), color.GetBlue()
        return convert_color(color, mode)
    except (TypeError, ReferenceError):
        pass
    raise ValueError("'{0!s}' is not a valid `color`".format(color))


class Color(_StyleContainer):
    """
    Container for grouping together ROOT and matplotlib colors.

    The *color* argument to the constructor can be a ROOT TColor or color index.
    If matplotlib is available, it can also accept an *RGB* or *RGBA* sequence,
    or a string in any of several forms:

        1) a letter from the set 'rgbcmykw'
        2) a hex color string, like '#00FFFF'
        3) a standard name, like 'aqua'
        4) a float, like '0.4', indicating gray on a 0-1 scale

    if *color* is *RGBA*, the *A* will simply be discarded.

    Examples
    --------

       >>> color = Color(2)
       >>> color()
       2
       >>> color('mpl')
       (1.0, 0.0, 0.0)
       >>> color = Color('blue')
       >>> color('root')
       4
       >>> color('mpl')
       (0.0, 0.0, 1.0)
       >>> color = Color('0.25')
       >>> color('mpl')
       (0.25, 0.25, 0.25)
       >>> color('root')
       924

    """
    def __init__(self, color):
        _StyleContainer.__init__(self, color, convert_color)