# Copyright (c) 2015, System Engineering Software Society
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#     * Redistributions of source code must retain the above copyright
#       notice, this list of conditions and the following disclaimer.
#     * Redistributions in binary form must reproduce the above copyright
#       notice, this list of conditions and the following disclaimer in the
#       documentation and/or other materials provided with the distribution.
#     * Neither the name of the System Engineering Software Society nor the
#       names of its contributors may be used to endorse or promote products
#       derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED.
# IN NO EVENT SHALL SYSTEM ENGINEERING SOFTWARE SOCIETY BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""
API for working with the Figure type.
Import this module like this::
    from sympathy.api import figure
.. _`markerstyles`:
Markers
-------
The methods :meth:`SyAxes.plot` allow the following marker styles:
    ======  ===========
    marker  description
    ======  ===========
    "o"	    circle
    "x"	    x
    "*"	    star
    ","	    pixel
    "."	    point
    "+"	    plus
    "D"	    diamond
    "s"	    square
    "_"	    hline
    "|"	    vline
    "^"	    triangle_up
    "d"	    thin_diamond
    "h"	    hexagon1
    "H"	    hexagon2
    "1"	    tri_down
    "2"	    tri_up
    "3"	    tri_left
    "4"	    tri_right
    "8"	    octagon
    "p"	    pentagon
    "v"	    triangle_down
    "<"	    triangle_left
    ">"	    triangle_right
    0	    tickleft
    1	    tickright
    2	    tickup
    3	    tickdown
    4	    caretleft
    5	    caretright
    6	    caretup
    7	    caretdown
    ""      nothing
    " "     nothing
    "None"	nothing
    None	nothing
    ======  ===========
.. _`colors`:
Colors
------
All color parameters accept the standard `matplotlib` colors formats:
- color names (see `Named colors
  <http://matplotlib.org/examples/color/named_colors.html>`_)
- RGB(A) colors as integer or float (e.g. (255, 255, 255) or (1., 1., 1., 1.))
- hex colors (e.g. '#eeefff')
For further information see matplotlibs
`color api <http://matplotlib.org/api/colors_api.html>`_.
.. _`location types`:
Location
--------
Whenever a location parameter is allowed, the following name strings
can be used.
    ===============   =============
    Location String   Location Code
    ===============   =============
    'best'            0 (only for legend)
    'upper right'     1
    'upper left'      2
    'lower left'      3
    'lower right'     4
    'right'           5
    'center left'     6
    'center right'    7
    'lower center'    8
    'upper center'    9
    'center'          10
    ===============   =============
Class :class:`figure.File`
--------------------------
.. autoclass:: File
   :members:
   :special-members:
Class :class:`figure.SyAxes`
----------------------------
.. autoclass:: SyAxes
    :members:
    :special-members:
Class :class:`figure.SyArtist`
------------------------------
.. autoclass:: SyArtist
    :members:
    :special-members:
"""
import os
import base64
import pickle
import warnings
import itertools
import collections
import numpy as np
from sympathy.platform.exceptions import sywarn
from ..utils import filebase
from ..utils.context import inherit_doc
def is_figure(scheme, filename):
    return File.is_type(filename, scheme)
def axes_wrapper(axes):
    if isinstance(axes, (collections.Sequence, np.ndarray)):
        wrapped = []
        for ax in axes:
            wrapped.append(axes_wrapper(ax))
        return wrapped
    else:
        return SyAxes(axes)
def _matplotlib():
    """
    Avoid pre-loading matplotlib since it takes too much time.
    Matplotlib import also has issues loading when python is installed on a
    unicode path causing unnecessary errors.
    """
    import matplotlib
    import matplotlib.backends.backend_agg
    import matplotlib.figure
    import matplotlib.tight_layout
    return matplotlib
[docs]@filebase.typeutil('sytypealias figure = sytext')
@inherit_doc
class File(filebase.TypeAlias):
    """
    A Figure.
    Any node port with the *Figure* type will produce an object of this type.
    """
[docs]    def init(self):
        self.__figure = None
        self.__ro_figure = None 
    @staticmethod
    def _decode(text_data):
        figure = pickle.loads(base64.decodestring(text_data.encode('ascii')))
        for ax in figure.axes:
            SyAxes(ax).rotate_xlabels_if_date()
        return figure
    @staticmethod
    def _encode(fig):
        return base64.encodestring(pickle.dumps(
            fig, protocol=pickle.HIGHEST_PROTOCOL)).decode('ascii')
    def __deepcopy_figure(self, figure=None):
        figure = figure or self.__figure
        if figure is not None:
            return self._decode(self._encode(figure))
        return None
[docs]    def source(self, other, shallow=False):
        """Copy the contents from ``other`` :class:`figure.File`.
        Equivalent to :meth:`update`.
        """
        self._data.source(other._data, shallow=shallow)
        self.__ro_figure = other._ro_figure
        self.__figure = None 
[docs]    def sync(self):
        figure = self.__figure or self.__ro_figure
        if figure is not None:
            self._data.set(self._encode(figure))
        self.__figure = None 
    def __copy__(self):
        raise NotImplementedError
[docs]    def __deepcopy__(self, memo=None):
        obj = super(File, self).__deepcopy__()
        obj.__ro_figure = self._ro_figure
        obj.__figure = None
        return obj 
    def _init_figure(self):
        if self.__figure is not None:
            return
        if self.__ro_figure:
            self.__figure = self.__deepcopy_figure(self.__ro_figure)
            self.__ro_figure = None
            return
        text_data = self._data.get()
        if text_data:
            self.__figure = self._decode(text_data)
        else:
            self.__figure = _matplotlib().figure.Figure(tight_layout=True)
    @property
    def _figure(self):
        self._init_figure()
        return self.__figure
    @property
    def _ro_figure(self):
        if self.__ro_figure is not None:
            return self.__ro_figure
        else:
            return self._figure
[docs]    @classmethod
    def viewer(cls):
        from .. platform import figure_viewer
        return figure_viewer.FigureViewer 
[docs]    @classmethod
    def icon(cls):
        return 'ports/figure.svg' 
    # Figure interface
    @property
    def axes(self):
        return axes_wrapper(self._figure.axes)
[docs]    def set_dpi(self, dpi):
        """Set the dots-per-inch of the figure."""
        return self._figure.set_dpi(dpi) 
[docs]    def set_title(self, title, fontsize=None):
        """
        Add a centered title to the figure.
        Parameters
        ----------
        fontsize : int or float or str, optional
            Controls the font size of the legend. If given as string,
            following strings are possible {'xx-small', 'x-small',
            'small', 'medium', 'large', 'x-large', 'xx-large'}.
            If the value is numeric the size will be the absolute
            font size in points. String values are relative to the
            current default font size.
        """
        return self._figure.suptitle(title, fontsize=fontsize) 
[docs]    def subplots(self, nrows, ncols, sharex=False, sharey=False):
        """
        Create subplot axes.
        Parameters
        ----------
        nrows : int
            Number of rows of subplots.
        ncols : int
            Number of columns of subplots.
        sharex : bool, optional
            Defines if x-axis are shared amongst all subplots.
        sharey : bool, optional
            Defines if y-axis are shared amongst all subplots.
        Returns
        -------
        axes : list
            Returns a list of :class:`SyAxes` axes.
        """
        axes = np.zeros((nrows, ncols), dtype=object)
        sharex_ax = [None] * ncols
        sharey_ax = [None] * nrows
        for i, (xi, yi) in enumerate(
                itertools.product(range(nrows), range(ncols))):
            sharex_arg = sharex_ax[yi] if sharex else None
            sharey_arg = sharey_ax[xi] if sharey else None
            ax = self._figure.add_subplot(
                nrows, ncols, i + 1, sharex=sharex_arg, sharey=sharey_arg)
            axes[xi, yi] = ax
            if sharex_ax[yi] is None:
                sharex_ax[yi] = ax
            if sharey_ax[xi] is None:
                sharey_ax[xi] = ax
        return axes_wrapper(axes) 
    def tight_layout(self):
        self._figure.tight_layout()
    def subplots_adjust(self, *args, **kwargs):
        self._figure.subplots_adjust(*args, **kwargs)
[docs]    def first_subplot(self):
        """Returns the first axes of the figure or creates one."""
        if self._figure.axes:
            ax = self._figure.axes[0]
        else:
            ax = self._figure.add_subplot(1, 1, 1)
        return SyAxes(ax) 
[docs]    def colorbar(self, artist, orientation='vertical', label=None,
                 fraction=0.05, extend=None):
        """
        Add a colorbar to a Figure.
        A row/column will be added to the bottom/right of the subplot Gridspec
        layout depending on the specified orientation (horizontal/vertical).
        Parameters
        ----------
        artist : matplotlib.artist.Artist
            The artist which the colorbar should be linked to.
        orientation : unicode, optional
            The orientation of the colorbar. Options: vertical or horizontal.
            Default: vertical
        label : unicode or None, optional
            The label added to the long axis of colorbar. Default: None
        fraction : float, optional
            The faction of the colorbar should take within the whole figure.
            Default: 0.05 (5%)
        """
        # TODO: This function should possibly also handle adding colorbars to
        # subplots and not only to the whole figure.
        # first we need to extend the GridSpec to add column/row for the
        # colorbar axes
        subplotspec_list = _matplotlib().tight_layout.get_subplotspec_list(
            self._figure.axes)
        subplotspec = subplotspec_list[0]
        gridspec = subplotspec.get_gridspec()
        ncols = int(gridspec._ncols)
        nrows = int(gridspec._nrows)
        if orientation == 'vertical':
            gridspec._ncols += 1
            for sps in subplotspec_list:
                num1 = sps.num1
                sps.num1 = num1 + (num1 // ncols)
            gridspec.set_width_ratios(
                [(1 - fraction) / ncols] * ncols + [fraction])
            gridspec.update()
            cax = self._figure.add_subplot(gridspec[:, ncols])
        else:
            gridspec._nrows += 1
            gridspec.set_height_ratios(
                [(1 - fraction) / nrows] * nrows + [fraction])
            gridspec.update()
            cax = self._figure.add_subplot(gridspec[nrows, :])
        # hook back the changed gridspec into all subplotspecs
        for sps in subplotspec_list:
            sps._gridspec = gridspec
        # create the colorbar
        if isinstance(artist, SyArtist):
            artist = artist.get_mpl_artist()
        kw = {'cax': cax,
              'orientation': orientation}
        if extend is not None:
            kw['extend'] = extend
        if label is not None:
            kw['label'] = label
        colorbar = self._figure.colorbar(artist, **kw)
        with warnings.catch_warnings():
            warnings.simplefilter("ignore")
            self._figure.tight_layout()
        return SyArtist(colorbar) 
[docs]    def rotate_xlabels_for_dates(self):
        """Rotates labels for all x axes if one is a datetime axis."""
        for syaxes in self.axes:
            syaxes.rotate_xlabels_if_date() 
    def _get_qtcanvas(self):
        """Warning: This method will create a QApplication. It should not be
        called in the execute method of any node.
        """
        from sympathy.api import qt
        qt.backend.use_matplotlib_qt()
        from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg
        return FigureCanvasQTAgg(self._ro_figure)
 
[docs]class SyAxes(object):
    """Wrapper around matplotlib Axes."""
    def __init__(self, axes):
        self._axes = axes
[docs]    def get_mpl_axes(self):
        """Return the underlying matplotlib Axes object.
        Warnings
        --------
           When using this function you will get access to the entire
           matplotlib API. That API is likely to change slightly over time
           completely outside of our control. If you want to be sure that the
           code you are writing continues to work as expected in upcoming
           versions of Sympathy you should not use this method.
        """
        return self._axes 
[docs]    def bar(self, left, height, width=0.8, bottom=None, color=None):
        """
        Make a bar plot.
        Make a bar plot with rectangles bounded by:
            left, left + width, bottom, bottom + height
                (left, right, bottom and top edges)
        Parameters
        ----------
        left : sequence of scalars
            the x coordinates of the left sides of the bars
        height : sequence of scalars
            the heights of the bars
        width : scalar or array-like, optional
            the width(s) of the bars
            default: 0.8
        bottom : scalar or array-like, optional
            the y coordinate(s) of the bars
            default: None
        color : scalar or array-like, optional
            face color of the bars
        Returns
        -------
        list
            List of :class:`SyArtist` s.
        """
        patches = self._axes.bar(left, height, width=width, bottom=bottom,
                                 color=color)
        return [SyArtist(patch) for patch in patches] 
[docs]    def hist(self, x, bins=10, range=None, normed=False, weights=None,
             cumulative=False, bottom=None, histtype='bar', align='mid',
             orientation='vertical', rwidth=None, log=False,
             color=None, label=None, stacked=False):
        """
        Plot a histogram.
        Compute and draw the histogram of *x*. The return value is a
        tuple (*n*, *bins*, *patches*) or ([*n0*, *n1*, ...], *bins*,
        [*patches0*, *patches1*,...]) if the input contains multiple
        data.
        Multiple data can be provided via *x* as a list of datasets
        of potentially different length ([*x0*, *x1*, ...]), or as
        a 2-D ndarray in which each column is a dataset.  Note that
        the ndarray form is transposed relative to the list form.
        Masked arrays are not supported at present.
        Parameters
        ----------
        x : (n,) array or sequence of (n,) arrays
            Input values, this takes either a single array or a sequency of
            arrays which are not required to be of the same length
        bins : integer or array_like, optional
            If an integer is given, `bins + 1` bin edges are returned,
            consistently with :func:`numpy.histogram` for numpy version >=
            1.3.
            Unequally spaced bins are supported if `bins` is a sequence.
            default is 10
        range : tuple or None, optional
            The lower and upper range of the bins. Lower and upper outliers
            are ignored. If not provided, `range` is (x.min(), x.max()). Range
            has no effect if `bins` is a sequence.
            If `bins` is a sequence or `range` is specified, autoscaling
            is based on the specified bin range instead of the
            range of x.
            Default is ``None``
        normed : boolean, optional
            If `True`, the first element of the return tuple will
            be the counts normalized to form a probability density, i.e.,
            ``n/(len(x)`dbin)``, i.e., the integral of the histogram will sum
            to 1. If *stacked* is also *True*, the sum of the histograms is
            normalized to 1.
            Default is ``False``
        weights : (n, ) array_like or None, optional
            An array of weights, of the same shape as `x`.  Each value in `x`
            only contributes its associated weight towards the bin count
            (instead of 1).  If `normed` is True, the weights are normalized,
            so that the integral of the density over the range remains 1.
            Default is ``None``
        cumulative : boolean, optional
            If `True`, then a histogram is computed where each bin gives the
            counts in that bin plus all bins for smaller values. The last bin
            gives the total number of datapoints.  If `normed` is also `True`
            then the histogram is normalized such that the last bin equals 1.
            If `cumulative` evaluates to less than 0 (e.g., -1), the direction
            of accumulation is reversed.  In this case, if `normed` is also
            `True`, then the histogram is normalized such that the first bin
            equals 1.
            Default is ``False``
        bottom : array_like, scalar, or None
            Location of the bottom baseline of each bin.  If a scalar,
            the base line for each bin is shifted by the same amount.
            If an array, each bin is shifted independently and the length
            of bottom must match the number of bins.  If None, defaults to 0.
            Default is ``None``
        histtype : {'bar', 'barstacked', 'step',  'stepfilled'}, optional
            The type of histogram to draw.
            - 'bar' is a traditional bar-type histogram.  If multiple data
              are given the bars are aranged side by side.
            - 'barstacked' is a bar-type histogram where multiple
              data are stacked on top of each other.
            - 'step' generates a lineplot that is by default
              unfilled.
            - 'stepfilled' generates a lineplot that is by default
              filled.
            Default is 'bar'
        align : {'left', 'mid', 'right'}, optional
            Controls how the histogram is plotted.
            - 'left': bars are centered on the left bin edges.
            - 'mid': bars are centered between the bin edges.
            - 'right': bars are centered on the right bin edges.
            Default is 'mid'
        orientation : {'horizontal', 'vertical'}, optional
            If 'horizontal', the *bottom* kwarg will be the left edges.
        rwidth : scalar or None, optional
            The relative width of the bars as a fraction of the bin width.  If
            `None`, automatically compute the width.
            Ignored if `histtype` is 'step' or 'stepfilled'.
            Default is ``None``
        log : boolean, optional
            If `True`, the histogram axis will be set to a log scale. If `log`
            is `True` and `x` is a 1D array, empty bins will be filtered out
            and only the non-empty (`n`, `bins`, `patches`) will be returned.
            Default is ``False``
        color : color or array_like of colors or None, optional
            Color spec or sequence of color specs, one per dataset.  Default
            (`None`) uses the standard line color sequence.
            Default is ``None``
        label : string or None, optional
            String, or sequence of strings to match multiple datasets.  Bar
            charts yield multiple patches per dataset, but only the first gets
            the label, so that the legend command will work as expected.
            default is ``None``
        stacked : boolean, optional
            If `True`, multiple data are stacked on top of each other If
            `False` multiple data are aranged side by side if histtype is
            'bar' or on top of each other if histtype is 'step'
            Default is ``False``
        Returns
        -------
        n : array or list of arrays
            The values of the histogram bins. See **normed** and **weights**
            for a description of the possible semantics. If input **x** is an
            array, then this is an array of length **nbins**. If input is a
            sequence arrays ``[data1, data2,..]``, then this is a list of
            arrays with the values of the histograms for each of the arrays
            in the same order.
        bins : array
            The edges of the bins. Length nbins + 1 (nbins left edges and right
            edge of last bin).  Always a single array even when multiple data
            sets are passed in.
        """
        n, bins, patches = self._axes.hist(x, bins, range, normed, weights,
                                           cumulative, bottom, histtype, align,
                                           orientation, rwidth, log,
                                           color, label, stacked)
        # TODO: return patches (can be a list or list of lists)
        return n, bins 
[docs]    def axvline(self, x=0, ymin=0, ymax=1, color=None,
                linewidth=1., linestyle='-'):
        """
        Add a vertical line across the axes.
        Parameters
        ----------
        x : scalar, optional, default: 0
            x position in data coordinates of the vertical line.
        ymin : scalar, optional, default: 0
            Should be between 0 and 1, 0 being the bottom
            of the plot, 1 the top of the plot.
        ymax : scalar, optional, default: 0
            Should be between 0 and 1, 0 being the bottom
            of the plot, 1 the top of the plot.
        linestyle : str, optional, default: '-'
            Any of ['solid' | 'dashed', 'dashdot', 'dotted' |
            '-' | '--' | '-.' | ':' | 'None' | ' ' | '']
        linewidth : float, optional
            Float value in points.
        color : color
            Any matplotlib color. See :ref:`colors`.
        Returns
        -------
        SyArtist
            Line2D wrapped in :class:`SyArtist`.
        """
        kwargs = {}
        if color is not None:
            kwargs['color'] = color
        if linewidth is not None:
            kwargs['linewidth'] = linewidth
        if linestyle is not None:
            kwargs['linestyle'] = linestyle
        return SyArtist(self._axes.axvline(x=x, ymin=ymin, ymax=ymax,
                                           **kwargs)) 
[docs]    def axhline(self, y=0, xmin=0, xmax=1, color=None,
                linewidth=1., linestyle='-'):
        """
        Add a horizontal line across the axis.
        Parameters
        ----------
        y : scalar, optional, default: 0
            y position in data coordinates of the horizontal line.
        xmin : scalar, optional, default: 0
            Should be between 0 and 1, 0 being the far left
            of the plot, 1 the far right of the plot.
        xmax : scalar, optional, default: 0
            Should be between 0 and 1, 0 being the far left
            of the plot, 1 the far right of the plot.
        linestyle : str, optional, default: '-'
            Any of ['solid' | 'dashed', 'dashdot', 'dotted' |
            '-' | '--' | '-.' | ':' | 'None' | ' ' | '']
        linewidth : float, optional
            Float value in points.
        color : color
            Any matplotlib color. See :ref:`colors`.
        Returns
        -------
        SyArtist
            Line2D wrapped in :class:`SyArtist`.
        """
        kwargs = {}
        if color is not None:
            kwargs['color'] = color
        if linewidth is not None:
            kwargs['linewidth'] = linewidth
        if linestyle is not None:
            kwargs['linestyle'] = linestyle
        return SyArtist(self._axes.axhline(y=y, xmin=xmin, xmax=xmax,
                                           **kwargs)) 
[docs]    def text(self, x, y, s, color=None, fontsize=None,
             bold=False, horizontalalignment='left',
             verticalalignment='top', data_coordinates=True):
        """
        Add text to the axes.
        Parameters
        ----------
        x : scalars
            x data coordinates.
        y : scalars
            y data coordinates.
        s : unicode
            The text to be shown.
        color : color, optional
            Any matplotlib color. See :ref:`colors`.
        bold : boolean, optional
            If ``True``, use a bold font. If ``False`` (the default), use a
            normal font.
        fontsize : int or float or str, optional
            Controls the font size of the legend. If given as string,
            following strings are possible {'xx-small', 'x-small',
            'small', 'medium', 'large', 'x-large', 'xx-large'}.
            If the value is numeric the size will be the absolute
            font size in points. String values are relative to the
            current default font size.
        horizontalalignment : {'center', 'right', 'left'}, optional
        verticalalignment : {'center', 'top', 'bottom', 'baseline'}, optional
        data_coordinates : bool, optional, default True
            If ``True``, `x` and `y` are assumed to be in data coordinates. If
            ``False``, they are assumed to be in axes coordinates.
        Returns
        -------
        SyArtist
            The text wrapped in a :class:`SyArtist`.
        """
        if data_coordinates:
            transform = self._axes.transData
        else:
            transform = self._axes.transAxes
        if bold:
            fontweight = 'bold'
        else:
            fontweight = 'normal'
        return SyArtist(self._axes.text(x=x, y=y, s=s, color=color,
                                        fontsize=fontsize,
                                        fontweight=fontweight,
                                        ha=horizontalalignment,
                                        va=verticalalignment,
                                        transform=transform)) 
[docs]    def scatter(self, x, y, size=20, color=None, marker='o',
                cmap=None, norm=None, vmin=None, vmax=None, alpha=None,
                linewidths=None, linestyles=None, edgecolors=None,
                label=None, zorder=None):
        """
        Make a scatter plot of x vs y, where x and y are sequence like objects
        of the same lengths.
        Parameters
        ----------
        x, y : array_like, shape (n, )
            Input data
        size : scalar or array_like, shape (n, ), optional, default: 20
            size in points^2.
        color : color or sequence of color, optional, default : 'b'
            `color` can be a single color format string, or a sequence of color
            specifications of length `N`, or a sequence of `N` numbers to be
            mapped to colors using the `cmap` and `norm` specified via kwargs
            (see below). Note that `color` should not be a single numeric RGB
            or RGBA sequence because that is indistinguishable from an array of
            values to be colormapped.  `color` can be a 2-D array in which the
            rows are RGB or RGBA, however, including the case of a single
            row to specify the same color for all points.
            See also :ref:`colors`.
        marker : marker, optional, default: 'o'
            See :ref:`markerstyles` for more information on the different
            styles of markers scatter supports. `marker` can be either
            an instance of the class or the text shorthand for a particular
            marker.
        cmap : `~matplotlib.colors.Colormap`, optional, default: None
            A `~matplotlib.colors.Colormap` instance or registered name.
            `cmap` is only used if `color` is an array of floats.
        norm : `~matplotlib.colors.Normalize`, optional, default: None
            A `~matplotlib.colors.Normalize` instance is used to scale
            luminance data to 0, 1. `norm` is only used if `color` is an array
            of floats. If `None`, use the default :func:`normalize`.
        vmin, vmax : scalar, optional, default: None
            `vmin` and `vmax` are used in conjunction with `norm` to normalize
            luminance data.  If either are `None`, the min and max of the
            color array is used.  Note if you pass a `norm` instance, your
            settings for `vmin` and `vmax` will be ignored.
        alpha : scalar, optional, default: None
            The alpha blending value, between 0 (transparent) and 1 (opaque)
        linewidths : scalar or array_like, optional, default: None
        edgecolors : color or sequence of color, optional, default: None
            If 'face', the edge color will always be the same as
            the face color.  If it is 'none', the patch boundary will not
            be drawn.  For non-filled markers, the `edgecolors` kwarg
            is ignored; color is determined by `color`.
        label : unicode, optional
            The label of the line.
        zorder : int, optional
            Define the z-order for this artist.
        """
        kwargs = {}
        if color is not None:
            kwargs['c'] = color
        if size is not None:
            kwargs['s'] = size
        if marker is not None:
            kwargs['marker'] = marker
        if label is not None:
            kwargs['label'] = label
        if cmap is not None:
            kwargs['cmap'] = cmap
        if norm is not None:
            kwargs['norm'] = norm
        if vmin is not None:
            kwargs['vmin'] = vmin
        if vmax is not None:
            kwargs['vmax'] = vmax
        if alpha is not None:
            kwargs['alpha'] = alpha
        if linewidths is not None:
            kwargs['linewidths'] = linewidths
        if linestyles is not None:
            kwargs['linestyles'] = linestyles
        if edgecolors is not None:
            kwargs['edgecolors'] = edgecolors
        if zorder is not None:
            kwargs['zorder'] = zorder
        scatter = self._axes.scatter(x, y, **kwargs)
        return SyArtist(scatter) 
[docs]    def plot(self, x, y, label=None, color=None,
             marker=None, markersize=None, markeredgecolor=None,
             markeredgewidth=None, markerfacecolor=None,
             linestyle='-', linewidth=1., alpha=1.,
             drawstyle='default', zorder=None):
        """
        Plot lines and/or markers to the axes.
        Parameters
        ----------
        x : array_like, shape (n,)
            The data for the x axis.
        y : array_like, shape (n,)
            The data for the y axis.
        label : unicode, optional
            The label of the line.
        color : color, optional
            Any matplotlib color. See :ref:`colors`.
        marker : str, optional
            Defines the marker style. See :ref:`Markers <markerstyles>` for
            all available markers.
        markersize : float, optional
            Defines the marker size.s
        markeredgecolor : color, optional
            Any matplotlib color. See :ref:`colors`.
        markeredgewidth : float, optional
            Float value in points.
        markerfacecolor : color, optional
            Any matplotlib color. See :ref:`colors`.
        linestyle : str, optional
            Any of ['solid' | 'dashed', 'dashdot', 'dotted' |
            '-' | '--' | '-.' | ':' | 'None' | ' ' | '']
        linewidth : float, optional
            Float value in points.
        alpha : float, optional
            Defines the transparency. Float value between 0. and 1.
            Default is 1.
        drawstyle : str, optional
            Defines the drawstyle of the plot. Accepts:
            ['default', 'steps', 'steps-pre', 'steps-mid',
            'steps-post']
        zorder : int, optional
            Define the z-order for this artist.
        Returns
        -------
        artists : list
            Returns a list of :class:`SyArtist` s.
        """
        kwargs = {}
        if color is not None:
            kwargs['color'] = color
        if marker is not None:
            kwargs['marker'] = marker
        if linestyle is not None:
            kwargs['linestyle'] = linestyle
        if alpha is not None:
            kwargs['alpha'] = alpha
        if markersize is not None:
            kwargs['markersize'] = markersize
        if markeredgecolor is not None:
            kwargs['markeredgecolor'] = markeredgecolor
        if markeredgewidth is not None:
            kwargs['markeredgewidth'] = markeredgewidth
        if markerfacecolor is not None:
            kwargs['markerfacecolor'] = markerfacecolor
        if linewidth is not None:
            kwargs['linewidth'] = linewidth
        if drawstyle is not None:
            kwargs['drawstyle'] = drawstyle
        if label is not None:
            kwargs['label'] = label
        if zorder is not None:
            kwargs['zorder'] = zorder
        artists = self._axes.plot(x, y, **kwargs)
        return [SyArtist(a) for a in artists] 
[docs]    def heatmap(self, x, y, c, cmap=None, vmin=None, vmax=None,
                edgecolor=None, linewidth=None):
        """
        Create a heatmap from
        Parameters
        ----------
        x : array_like, shape (m+1,)
            *x* is 1D arrays of length *nc* +1 giving the x boundaries
            of the cells.
        y : array_like, shape (n+1,)
            *y* is 1D arrays of length *nr* +1 giving the y boundaries
            of the cells.
        c : array_like, shape (m, n)
            A 2D array of color values.
        cmap : { *None* | Colormap }
            A :class:`matplotlib.colors.Colormap` instance. If *None*, use
            rc settings.
        edgecolor: {*None* | ``'None'`` | ``'face'`` | color | color sequence}
            If *None*, the rc setting is used by default.
            If ``'None'``, edges will not be visible.
            If ``'face'``, edges will have the same color as the faces.
            An mpl color or sequence of colors will set the edge color
        linewidth : float, optional
            Float value in points.
        Returns
        -------
        SyArtist
            :class:`SyArtist` of the matplotlib collection.
        """
        return SyArtist(self._axes.pcolormesh(x, y, c,
                                              cmap=cmap,
                                              vmin=vmin,
                                              vmax=vmax,
                                              edgecolor=edgecolor,
                                              linewidth=linewidth)) 
[docs]    def twinx(self):
        """Create a twin SyAxes with shared x-axis."""
        return SyAxes(self._axes.twinx()) 
[docs]    def twiny(self):
        """Create a twin SyAxes with shared y-axis."""
        return SyAxes(self._axes.twiny()) 
[docs]    def grid(self, b=None, which='major', axis='both',
             color=None, linestyle=None, linewidth=None):
        """
        Turn the axes grids on or off.
        Parameters
        ----------
        b : bool, optional
            Set the axes grids on or off. *None* with *len(kwargs)*
            toggles the grid state.
        which : str, optional
            Can be 'major' (default), 'minor', or 'both'. Controls
            whether only major, only minor, or both tick grids are
            affected.
        axis : str, optional
            Can be 'both' (default), 'x', or 'y'. Controls which set
            of gridlines are drawn.
        color : color, optional
            Any matplotlib color. See :ref:`colors`.
        linestyle : str
            Any of ['solid' | 'dashed', 'dashdot', 'dotted' |
            '-' | '--' | '-.' | ':' | 'None' | ' ' | '']
        linewidth : float, optional
            Float value in points.
        """
        supported_linestyles = ['solid', 'dashed', 'dashdot', 'dotted',
                                '-', '--', '-.', ':', 'None', ' ', '']
        kwargs = {}
        if color is not None:
            kwargs['color'] = color
        if linestyle is not None and linestyle in supported_linestyles:
            kwargs['linestyle'] = linestyle
        if linewidth is not None and isinstance(linewidth, (int, float)):
            kwargs['linewidth'] = linewidth
        self._axes.grid(b, which, axis, **kwargs) 
[docs]    def set_axis(self, state):
        """Sets the axes frame/ticks/labels visibility on/off."""
        if state:
            self._axes.set_axis_on()
        else:
            self._axes.set_axis_off() 
[docs]    def set_xlim(self, left=None, right=None):
        """
        Set the data limits for the x-axis.
        Examples
        --------
        >>> set_xlim((left, right))
        >>> set_xlim(left, right)
        >>> set_xlim(left=1)  # right unchanged
        >>> set_xlim(right=1)  # left unchanged
        """
        self._axes.set_xlim(left, right) 
[docs]    def set_ylim(self, bottom=None, top=None):
        """
        Set the data limits for the y-axis.
        Examples
        --------
        >>> set_ylim((bottom, top))
        >>> set_ylim(bottom, top)
        >>> set_ylim(bottom=1)  # top unchanged
        >>> set_ylim(top=1)  # bottom unchanged
        """
        self._axes.set_ylim(bottom, top) 
[docs]    def set_xticklabels(self, labels):
        """Set the x-tick labels with list of strings *labels*."""
        self._axes.set_xticklabels(labels) 
[docs]    def set_yticklabels(self, labels):
        """Set the y-tick labels with list of strings *labels*."""
        self._axes.set_yticklabels(labels) 
[docs]    def set_title(self, title, fontsize=None):
        """
        Set the title of the axes.
        Parameters
        ----------
        fontsize : int or float or str, optional
            Controls the font size of the legend. If given as string,
            following strings are possible {'xx-small', 'x-small',
            'small', 'medium', 'large', 'x-large', 'xx-large'}.
            If the value is numeric the size will be the absolute
            font size in points. String values are relative to the
            current default font size.
        """
        self._axes.set_title(title, fontsize=fontsize) 
[docs]    def set_xlabel(self, label, fontsize=None):
        """
        Set the label for the x-axis.
        Parameters
        ----------
        fontsize : int or float or str, optional
            Controls the font size of the legend. If given as string,
            following strings are possible {'xx-small', 'x-small',
            'small', 'medium', 'large', 'x-large', 'xx-large'}.
            If the value is numeric the size will be the absolute
            font size in points. String values are relative to the
            current default font size.
        """
        self._axes.set_xlabel(label, fontsize=fontsize) 
[docs]    def set_ylabel(self, label, fontsize=None):
        """
        Set the label for the y-axis.
        Parameters
        ----------
        fontsize : int or float or str, optional
            Controls the font size of the legend. If given as string,
            following strings are possible {'xx-small', 'x-small',
            'small', 'medium', 'large', 'x-large', 'xx-large'}.
            If the value is numeric the size will be the absolute
            font size in points. String values are relative to the
            current default font size.
        """
        self._axes.set_ylabel(label, fontsize=fontsize) 
[docs]    def legend(self, handles=None, labels=None, loc='upper right',
               ncol=1, fontsize=None, frameon=None, title=None):
        """
        Places a legend on the axes.
        Parameters
        ----------
        handles : array_like, shape (n,)
            List of Artist handles.
        labels : array_like, shape (n,)
            List of Artist labels.
        loc : str or int, optional
            The location of the legend. See :ref:`location types`.
            Default is 'upper right'.
        ncol : int, optional
            The number of columns that the legend has. Default is 1.
        fontsize : int or float or str, optional
            Controls the font size of the legend. If given as string,
            following strings are possible {'xx-small', 'x-small',
            'small', 'medium', 'large', 'x-large', 'xx-large'}.
            If the value is numeric the size will be the absolute
            font size in points. String values are relative to the
            current default font size.
        frameon : bool or None, optional
            Controls whether a frame should be drawn around the
            legend. Default is None which will take the value from
            the legend.frameon rcParam.
        title : str or None, optional
            The legend's title. Default is no title (*None*).
        """
        kwargs = {}
        if handles is not None and labels is not None:
            kwargs['handles'] = handles
            kwargs['labels'] = labels
        if loc is not None:
            kwargs['loc'] = loc
        if ncol is not None:
            kwargs['ncol'] = ncol
        if fontsize is not None:
            kwargs['fontsize'] = fontsize
        if frameon is not None:
            kwargs['frameon'] = frameon
        if title is not None:
            kwargs['title'] = title
        self._axes.legend(**kwargs) 
    def rotate_xlabels_if_date(self, ha='right', rotation=30):
        from matplotlib import dates
        axes = self.get_mpl_axes()
        if isinstance(axes.xaxis.get_major_locator(), dates.AutoDateLocator):
            for label in axes.get_xticklabels():
                label.set_ha(ha)
                label.set_rotation(rotation)
            return True
        return False
    def __eq__(self, other):
        if isinstance(other, SyAxes):
            return self._axes == other._axes
        return NotImplemented
    def __ne__(self, other):
        result = self.__eq__(other)
        if result is NotImplemented:
            return result
        return not result 
[docs]class SyArtist(object):
    """Wrapper around matplotlib Artist."""
    def __init__(self, artist):
        self._artist = artist
[docs]    def get_mpl_artist(self):
        """Returns the underlying matplotlib Artist object.
        Warnings
        --------
           When using this function you will get access to the entire
           matplotlib API. That API is likely to change slightly over time
           completely outside of our control. If you want to be sure that the
           code you are writing continues to work as expected in upcoming
           versions of Sympathy you should not use this method.
        """
        return self._artist 
    def __eq__(self, other):
        if isinstance(other, SyArtist):
            return self._artist == other._artist
        return NotImplemented
    def __ne__(self, other):
        result = self.__eq__(other)
        if result is NotImplemented:
            return result
        return not result