"""
This module contains all the 2D line class which can draw with a
variety of line styles, markers and colors.
"""

# TODO: expose cap and join style attrs
from __future__ import division

import numpy as np
from numpy import ma
from matplotlib import verbose
import artist
from artist import Artist
from cbook import iterable, is_string_like, is_numlike, ls_mapper, dedent,\
flatten, is_math_text
from colors import colorConverter
from path import Path
from transforms import Affine2D, Bbox, TransformedPath, IdentityTransform

from matplotlib import rcParams
from artist import allow_rasterization
from matplotlib import docstring
from matplotlib.font_manager import FontProperties
from matplotlib.markers import MarkerStyle
# Imported here for backward compatibility, even though they don't
# really belong.
from matplotlib.markers import TICKLEFT, TICKRIGHT, TICKUP, TICKDOWN, \
         CARETLEFT, CARETRIGHT, CARETUP, CARETDOWN

def segment_hits(cx, cy, x, y, radius):
    """
    Determine if any line segments are within radius of a
    point. Returns the list of line segments that are within that
    radius.
    """
    # Process single points specially
    if len(x) < 2:
        res, = np.nonzero( (cx - x)**2 + (cy - y)**2 <= radius**2 )
        return res

    # We need to lop the last element off a lot.
    xr,yr = x[:-1],y[:-1]

    # Only look at line segments whose nearest point to C on the line
    # lies within the segment.
    dx,dy = x[1:]-xr, y[1:]-yr
    Lnorm_sq = dx**2+dy**2    # Possibly want to eliminate Lnorm==0
    u = ( (cx-xr)*dx + (cy-yr)*dy )/Lnorm_sq
    candidates = (u>=0) & (u<=1)
    #if any(candidates): print "candidates",xr[candidates]

    # Note that there is a little area near one side of each point
    # which will be near neither segment, and another which will
    # be near both, depending on the angle of the lines.  The
    # following radius test eliminates these ambiguities.
    point_hits = (cx - x)**2 + (cy - y)**2 <= radius**2
    #if any(point_hits): print "points",xr[candidates]
    candidates = candidates & ~(point_hits[:-1] | point_hits[1:])

    # For those candidates which remain, determine how far they lie away
    # from the line.
    px,py = xr+u*dx,yr+u*dy
    line_hits = (cx-px)**2 + (cy-py)**2 <= radius**2
    #if any(line_hits): print "lines",xr[candidates]
    line_hits = line_hits & candidates
    points, = point_hits.ravel().nonzero()
    lines, = line_hits.ravel().nonzero()
    #print points,lines
    return np.concatenate((points,lines))

class Line2D(Artist):
    """
    A line - the line can have both a solid linestyle connecting all
    the vertices, and a marker at each vertex.  Additionally, the
    drawing of the solid line is influenced by the drawstyle, eg one
    can create "stepped" lines in various styles.


    """
    lineStyles = _lineStyles =  { # hidden names deprecated
        '-'          : '_draw_solid',
        '--'         : '_draw_dashed',
        '-.'         : '_draw_dash_dot',
        ':'          : '_draw_dotted',
        'None'       : '_draw_nothing',
        ' '          : '_draw_nothing',
        ''           : '_draw_nothing',
    }

    _drawStyles_l = {
        'default'    : '_draw_lines',
        'steps-mid'  : '_draw_steps_mid',
        'steps-pre'  : '_draw_steps_pre',
        'steps-post' : '_draw_steps_post',
    }

    _drawStyles_s = {
        'steps'      : '_draw_steps_pre',
    }
    drawStyles = {}
    drawStyles.update(_drawStyles_l)
    drawStyles.update(_drawStyles_s)
    # Need a list ordered with long names first:
    drawStyleKeys = _drawStyles_l.keys() + _drawStyles_s.keys()

    # Referenced here to maintain API.  These are defined in
    # MarkerStyle
    markers = MarkerStyle.markers
    filled_markers = MarkerStyle.filled_markers
    fillStyles = MarkerStyle.fillstyles

    zorder = 2
    validCap = ('butt', 'round', 'projecting')
    validJoin =   ('miter', 'round', 'bevel')

    def __str__(self):
        if self._label != "":
            return "Line2D(%s)"%(self._label)
        elif hasattr(self, '_x') and len(self._x) > 3:
            return "Line2D((%g,%g),(%g,%g),...,(%g,%g))"\
                %(self._x[0],self._y[0],self._x[0],self._y[0],self._x[-1],self._y[-1])
        elif hasattr(self, '_x'):
            return "Line2D(%s)"\
                %(",".join(["(%g,%g)"%(x,y) for x,y in zip(self._x,self._y)]))
        else:
            return "Line2D()"

    def __init__(self, xdata, ydata,
                 linewidth       = None, # all Nones default to rc
                 linestyle       = None,
                 color           = None,
                 marker          = None,
                 markersize      = None,
                 markeredgewidth = None,
                 markeredgecolor = None,
                 markerfacecolor = None,
                 markerfacecoloralt = 'none',
                 fillstyle       = 'full',
                 antialiased     = None,
                 dash_capstyle   = None,
                 solid_capstyle  = None,
                 dash_joinstyle  = None,
                 solid_joinstyle = None,
                 pickradius      = 5,
                 drawstyle       = None,
                 markevery       = None,
                 **kwargs
                 ):
        """
        Create a :class:`~matplotlib.lines.Line2D` instance with *x*
        and *y* data in sequences *xdata*, *ydata*.

        The kwargs are :class:`~matplotlib.lines.Line2D` properties:

        %(Line2D)s

        See :meth:`set_linestyle` for a decription of the line styles,
        :meth:`set_marker` for a description of the markers, and
        :meth:`set_drawstyle` for a description of the draw styles.

        """
        Artist.__init__(self)

        #convert sequences to numpy arrays
        if not iterable(xdata):
            raise RuntimeError('xdata must be a sequence')
        if not iterable(ydata):
            raise RuntimeError('ydata must be a sequence')

        if linewidth is None   : linewidth=rcParams['lines.linewidth']

        if linestyle is None   : linestyle=rcParams['lines.linestyle']
        if marker is None      : marker=rcParams['lines.marker']
        if color is None       : color=rcParams['lines.color']

        if markersize is None  : markersize=rcParams['lines.markersize']
        if antialiased is None : antialiased=rcParams['lines.antialiased']
        if dash_capstyle is None : dash_capstyle=rcParams['lines.dash_capstyle']
        if dash_joinstyle is None : dash_joinstyle=rcParams['lines.dash_joinstyle']
        if solid_capstyle is None : solid_capstyle=rcParams['lines.solid_capstyle']
        if solid_joinstyle is None : solid_joinstyle=rcParams['lines.solid_joinstyle']

        if drawstyle is None : drawstyle='default'

        self.set_dash_capstyle(dash_capstyle)
        self.set_dash_joinstyle(dash_joinstyle)
        self.set_solid_capstyle(solid_capstyle)
        self.set_solid_joinstyle(solid_joinstyle)


        self.set_linestyle(linestyle)
        self.set_drawstyle(drawstyle)
        self.set_linewidth(linewidth)
        self.set_color(color)
        self._marker = MarkerStyle()
        self.set_marker(marker)
        self.set_markevery(markevery)
        self.set_antialiased(antialiased)
        self.set_markersize(markersize)
        self._dashSeq = None


        self.set_markerfacecolor(markerfacecolor)
        self.set_markerfacecoloralt(markerfacecoloralt)
        self.set_markeredgecolor(markeredgecolor)
        self.set_markeredgewidth(markeredgewidth)
        self.set_fillstyle(fillstyle)

        self.verticalOffset = None

        # update kwargs before updating data to give the caller a
        # chance to init axes (and hence unit support)
        self.update(kwargs)
        self.pickradius = pickradius
        self.ind_offset = 0
        if is_numlike(self._picker):
            self.pickradius = self._picker

        self._xorig = np.asarray([])
        self._yorig = np.asarray([])
        self._invalidx = True
        self._invalidy = True
        self.set_data(xdata, ydata)

    def contains(self, mouseevent):
        """
        Test whether the mouse event occurred on the line.  The pick
        radius determines the precision of the location test (usually
        within five points of the value).  Use
        :meth:`~matplotlib.lines.Line2D.get_pickradius` or
        :meth:`~matplotlib.lines.Line2D.set_pickradius` to view or
        modify it.

        Returns *True* if any values are within the radius along with
        ``{'ind': pointlist}``, where *pointlist* is the set of points
        within the radius.

        TODO: sort returned indices by distance
        """
        if callable(self._contains): return self._contains(self,mouseevent)

        if not is_numlike(self.pickradius):
            raise ValueError,"pick radius should be a distance"

        # Make sure we have data to plot
        if self._invalidy or self._invalidx:
            self.recache()
        if len(self._xy)==0: return False,{}

        # Convert points to pixels
        if self._transformed_path is None:
            self._transform_path()
        path, affine = self._transformed_path.get_transformed_path_and_affine()
        path = affine.transform_path(path)
        xy = path.vertices
        xt = xy[:, 0]
        yt = xy[:, 1]

        # Convert pick radius from points to pixels
        if self.figure == None:
            warning.warn('no figure set when check if mouse is on line')
            pixels = self.pickradius
        else:
            pixels = self.figure.dpi/72. * self.pickradius

        # Check for collision
        if self._linestyle in ['None',None]:
            # If no line, return the nearby point(s)
            d = (xt-mouseevent.x)**2 + (yt-mouseevent.y)**2
            ind, = np.nonzero(np.less_equal(d, pixels**2))
        else:
            # If line, return the nearby segment(s)
            ind = segment_hits(mouseevent.x,mouseevent.y,xt,yt,pixels)

        ind += self.ind_offset

        # Debugging message
        if False and self._label != u'':
            print "Checking line",self._label,"at",mouseevent.x,mouseevent.y
            print 'xt', xt
            print 'yt', yt
            #print 'dx,dy', (xt-mouseevent.x)**2., (yt-mouseevent.y)**2.
            print 'ind',ind

        # Return the point(s) within radius
        return len(ind)>0,dict(ind=ind)

    def get_pickradius(self):
        'return the pick radius used for containment tests'
        return self.pickradius

    def set_pickradius(self,d):
        """Sets the pick radius used for containment tests

        ACCEPTS: float distance in points
        """
        self.pickradius = d

    def get_fillstyle(self):
        """
        return the marker fillstyle
        """
        return self._marker.get_fillstyle()

    def set_fillstyle(self, fs):
        """
        Set the marker fill style; 'full' means fill the whole marker.
        The other options are for half filled markers

        ACCEPTS: ['full' | 'left' | 'right' | 'bottom' | 'top']
        """
        self._marker.set_fillstyle(fs)

    def set_markevery(self, every):
        """
        Set the markevery property to subsample the plot when using
        markers.  Eg if ``markevery=5``, every 5-th marker will be
        plotted.  *every* can be

        None
            Every point will be plotted

        an integer N
            Every N-th marker will be plotted starting with marker 0

        A length-2 tuple of integers
            every=(start, N) will start at point start and plot every N-th marker


        ACCEPTS: None | integer | (startind, stride)

        """
        self._markevery = every

    def get_markevery(self):
        'return the markevery setting'
        return self._markevery

    def set_picker(self,p):
        """Sets the event picker details for the line.

        ACCEPTS: float distance in points or callable pick function
        ``fn(artist, event)``
        """
        if callable(p):
            self._contains = p
        else:
            self.pickradius = p
        self._picker = p

    def get_window_extent(self, renderer):
        bbox = Bbox.unit()
        bbox.update_from_data_xy(self.get_transform().transform(self.get_xydata()),
                                 ignore=True)
        # correct for marker size, if any
        if self._marker:
            ms = (self._markersize / 72.0 * self.figure.dpi) * 0.5
            bbox = bbox.padded(ms)
        return bbox

    def set_axes(self, ax):
        Artist.set_axes(self, ax)
        if ax.xaxis is not None:
            self._xcid = ax.xaxis.callbacks.connect('units', self.recache_always)
        if ax.yaxis is not None:
            self._ycid = ax.yaxis.callbacks.connect('units', self.recache_always)
    set_axes.__doc__ = Artist.set_axes.__doc__

    def set_data(self, *args):
        """
        Set the x and y data

        ACCEPTS: 2D array (rows are x, y) or two 1D arrays
        """
        if len(args)==1:
            x, y = args[0]
        else:
            x, y = args

        self.set_xdata(x)
        self.set_ydata(y)

    def recache_always(self):
        self.recache(always=True)

    def recache(self, always=False):
        if always or self._invalidx:
            xconv = self.convert_xunits(self._xorig)
            if ma.isMaskedArray(self._xorig):
                x = ma.asarray(xconv, np.float_)
            else:
                x = np.asarray(xconv, np.float_)
            x = x.ravel()
        else:
            x = self._x
        if always or self._invalidy:
            yconv = self.convert_yunits(self._yorig)
            if ma.isMaskedArray(self._yorig):
                y = ma.asarray(yconv, np.float_)
            else:
                y = np.asarray(yconv, np.float_)
            y = y.ravel()
        else:
            y = self._y

        if len(x)==1 and len(y)>1:
            x = x * np.ones(y.shape, np.float_)
        if len(y)==1 and len(x)>1:
            y = y * np.ones(x.shape, np.float_)

        if len(x) != len(y):
            raise RuntimeError('xdata and ydata must be the same length')

        x = x.reshape((len(x), 1))
        y = y.reshape((len(y), 1))

        if ma.isMaskedArray(x) or ma.isMaskedArray(y):
            self._xy = ma.concatenate((x, y), 1)
        else:
            self._xy = np.concatenate((x, y), 1)
        self._x = self._xy[:, 0] # just a view
        self._y = self._xy[:, 1] # just a view

        self._subslice = False
        if (self.axes and len(x) > 100 and self._is_sorted(x) and
            self.axes.name == 'rectilinear' and
            self.axes.get_xscale() == 'linear' and
            self._markevery is None):
            self._subslice = True
        if hasattr(self, '_path'):
            interpolation_steps = self._path._interpolation_steps
        else:
            interpolation_steps = 1
        self._path = Path(self._xy, None, interpolation_steps)
        self._transformed_path = None
        self._invalidx = False
        self._invalidy = False

    def _transform_path(self, subslice=None):
        # Masked arrays are now handled by the Path class itself
        if subslice is not None:
            _path = Path(self._xy[subslice,:])
        else:
            _path = self._path
        self._transformed_path = TransformedPath(_path, self.get_transform())


    def set_transform(self, t):
        """
        set the Transformation instance used by this artist

        ACCEPTS: a :class:`matplotlib.transforms.Transform` instance
        """
        Artist.set_transform(self, t)
        self._invalidx = True
        self._invalidy = True

    def _is_sorted(self, x):
        "return true if x is sorted"
        if len(x)<2: return 1
        return np.alltrue(x[1:]-x[0:-1]>=0)

    @allow_rasterization
    def draw(self, renderer):
        if self._invalidy or self._invalidx:
            self.recache()
        self.ind_offset = 0  # Needed for contains() method.
        if self._subslice and self.axes:
            # Need to handle monotonically decreasing case also...
            x0, x1 = self.axes.get_xbound()
            i0, = self._x.searchsorted([x0], 'left')
            i1, = self._x.searchsorted([x1], 'right')
            subslice = slice(max(i0-1, 0), i1+1)
            self.ind_offset = subslice.start
            self._transform_path(subslice)
        if self._transformed_path is None:
            self._transform_path()

        if not self.get_visible(): return

        renderer.open_group('line2d', self.get_gid())
        gc = renderer.new_gc()
        self._set_gc_clip(gc)

        gc.set_foreground(self._color)
        gc.set_antialiased(self._antialiased)
        gc.set_linewidth(self._linewidth)
        gc.set_alpha(self._alpha)
        if self.is_dashed():
            cap = self._dashcapstyle
            join = self._dashjoinstyle
        else:
            cap = self._solidcapstyle
            join = self._solidjoinstyle
        gc.set_joinstyle(join)
        gc.set_capstyle(cap)
        gc.set_snap(self.get_snap())

        funcname = self._lineStyles.get(self._linestyle, '_draw_nothing')
        if funcname != '_draw_nothing':
            tpath, affine = self._transformed_path.get_transformed_path_and_affine()
            if len(tpath.vertices):
                self._lineFunc = getattr(self, funcname)
                funcname = self.drawStyles.get(self._drawstyle, '_draw_lines')
                drawFunc = getattr(self, funcname)
                drawFunc(renderer, gc, tpath, affine.frozen())

        if self._marker:
            gc = renderer.new_gc()
            self._set_gc_clip(gc)
            gc.set_foreground(self.get_markeredgecolor())
            gc.set_linewidth(self._markeredgewidth)
            gc.set_alpha(self._alpha)
            marker = self._marker
            tpath, affine = self._transformed_path.get_transformed_points_and_affine()
            if len(tpath.vertices):
                # subsample the markers if markevery is not None
                markevery = self.get_markevery()
                if markevery is not None:
                    if iterable(markevery):
                        startind, stride = markevery
                    else:
                        startind, stride = 0, markevery
                    if tpath.codes is not None:
                        codes = tpath.codes[startind::stride]
                    else:
                        codes = None
                    vertices = tpath.vertices[startind::stride]
                    subsampled = Path(vertices, codes)
                else:
                    subsampled = tpath

                snap = marker.get_snap_threshold()
                if type(snap) == float:
                    snap = renderer.points_to_pixels(self._markersize) >= snap
                gc.set_snap(snap)
                marker_path = marker.get_path()
                marker_trans = marker.get_transform()
                w = renderer.points_to_pixels(self._markersize)
                if marker.get_marker() != ',': # Don't scale for pixels
                    marker_trans = marker_trans.scale(w)
                rgbFace = self._get_rgb_face()
                renderer.draw_markers(
                    gc, marker_path, marker_trans, subsampled, affine.frozen(),
                    rgbFace)
                alt_marker_path = marker.get_alt_path()
                if alt_marker_path:
                    alt_marker_trans = marker.get_alt_transform()
                    alt_marker_trans = alt_marker_trans.scale(w)
                    rgbFace = self._get_rgb_face(alt=True)
                    renderer.draw_markers(
                        gc, alt_marker_path, alt_marker_trans, subsampled,
                        affine.frozen(), rgbFace)

            gc.restore()

        gc.restore()
        renderer.close_group('line2d')

    def get_antialiased(self): return self._antialiased
    def get_color(self): return self._color
    def get_drawstyle(self): return self._drawstyle
    def get_linestyle(self): return self._linestyle

    def get_linewidth(self): return self._linewidth
    def get_marker(self): return self._marker.get_marker()

    def get_markeredgecolor(self):
        if (is_string_like(self._markeredgecolor) and
            self._markeredgecolor == 'auto'):
            if self._marker.is_filled():
                return 'k'
            else:
                return self._color
        else:
            return self._markeredgecolor


        return self._markeredgecolor
    def get_markeredgewidth(self): return self._markeredgewidth

    def _get_markerfacecolor(self, alt=False):
        if alt:
            fc = self._markerfacecoloralt
        else:
            fc = self._markerfacecolor

        if (fc is None or (is_string_like(fc) and fc.lower()=='none') ):
            return fc
        elif (is_string_like(fc) and fc.lower() == 'auto'):
            return self._color
        else:
            return fc

    def get_markerfacecolor(self):
        return self._get_markerfacecolor(alt=False)

    def get_markerfacecoloralt(self):
        return self._get_markerfacecolor(alt=True)

    def get_markersize(self): return self._markersize

    def get_data(self, orig=True):
        """
        Return the xdata, ydata.

        If *orig* is *True*, return the original data
        """
        return self.get_xdata(orig=orig), self.get_ydata(orig=orig)


    def get_xdata(self, orig=True):
        """
        Return the xdata.

        If *orig* is *True*, return the original data, else the
        processed data.
        """
        if orig:
            return self._xorig
        if self._invalidx:
            self.recache()
        return self._x

    def get_ydata(self, orig=True):
        """
        Return the ydata.

        If *orig* is *True*, return the original data, else the
        processed data.
        """
        if orig:
            return self._yorig
        if self._invalidy:
            self.recache()
        return self._y

    def get_path(self):
        """
        Return the :class:`~matplotlib.path.Path` object associated
        with this line.
        """
        if self._invalidy or self._invalidx:
            self.recache()
        return self._path

    def get_xydata(self):
        """
        Return the *xy* data as a Nx2 numpy array.
        """
        if self._invalidy or self._invalidx:
            self.recache()
        return self._xy

    def set_antialiased(self, b):
        """
        True if line should be drawin with antialiased rendering

        ACCEPTS: [True | False]
        """
        self._antialiased = b

    def set_color(self, color):
        """
        Set the color of the line

        ACCEPTS: any matplotlib color
        """
        self._color = color

    def set_drawstyle(self, drawstyle):
        """
        Set the drawstyle of the plot

        'default' connects the points with lines. The steps variants
        produce step-plots. 'steps' is equivalent to 'steps-pre' and
        is maintained for backward-compatibility.

        ACCEPTS: [ 'default' | 'steps' | 'steps-pre' | 'steps-mid' | 'steps-post' ]
        """
        self._drawstyle = drawstyle

    def set_linewidth(self, w):
        """
        Set the line width in points

        ACCEPTS: float value in points
        """
        self._linewidth = w

    def set_linestyle(self, linestyle):
        """
        Set the linestyle of the line (also accepts drawstyles)


        ================    =================
        linestyle           description
        ================    =================
        ``'-'``             solid
        ``'--'``            dashed
        ``'-.'``            dash_dot
        ``':'``             dotted
        ``'None'``          draw nothing
        ``' '``             draw nothing
        ``''``              draw nothing
        ================    =================

        'steps' is equivalent to 'steps-pre' and is maintained for
        backward-compatibility.

        .. seealso::

            :meth:`set_drawstyle`
               To set the drawing style (stepping) of the plot.

        ACCEPTS: [ ``'-'`` | ``'--'`` | ``'-.'`` | ``':'`` | ``'None'`` | ``' '`` | ``''`` ]
        and any drawstyle in combination with a linestyle, e.g. ``'steps--'``.
        """

        for ds in self.drawStyleKeys:  # long names are first in the list
            if linestyle.startswith(ds):
                self.set_drawstyle(ds)
                if len(linestyle) > len(ds):
                    linestyle = linestyle[len(ds):]
                else:
                    linestyle = '-'
                break

        if linestyle not in self._lineStyles:
            if linestyle in ls_mapper:
                linestyle = ls_mapper[linestyle]
            else:
                verbose.report('Unrecognized line style %s, %s' %
                                            (linestyle, type(linestyle)))
        if linestyle in [' ','']:
            linestyle = 'None'
        self._linestyle = linestyle

    @docstring.dedent_interpd
    def set_marker(self, marker):
        """
        Set the line marker

        %(MarkerTable)s

        %(MarkerAccepts)s
        """
        self._marker.set_marker(marker)

    def set_markeredgecolor(self, ec):
        """
        Set the marker edge color

        ACCEPTS: any matplotlib color
        """
        if ec is None :
            ec = 'auto'
        self._markeredgecolor = ec

    def set_markeredgewidth(self, ew):
        """
        Set the marker edge width in points

        ACCEPTS: float value in points
        """
        if ew is None :
            ew = rcParams['lines.markeredgewidth']
        self._markeredgewidth = ew

    def set_markerfacecolor(self, fc):
        """
        Set the marker face color.

        ACCEPTS: any matplotlib color
        """
        if fc is None:
            fc = 'auto'

        self._markerfacecolor = fc

    def set_markerfacecoloralt(self, fc):
        """
        Set the alternate marker face color.

        ACCEPTS: any matplotlib color
        """
        if fc is None:
            fc = 'auto'

        self._markerfacecoloralt = fc

    def set_markersize(self, sz):
        """
        Set the marker size in points

        ACCEPTS: float
        """
        self._markersize = sz

    def set_xdata(self, x):
        """
        Set the data np.array for x

        ACCEPTS: 1D array
        """
        self._xorig = x
        self._invalidx = True

    def set_ydata(self, y):
        """
        Set the data np.array for y

        ACCEPTS: 1D array
        """
        self._yorig = y
        self._invalidy = True

    def set_dashes(self, seq):
        """
        Set the dash sequence, sequence of dashes with on off ink in
        points.  If seq is empty or if seq = (None, None), the
        linestyle will be set to solid.

        ACCEPTS: sequence of on/off ink in points
        """
        if seq == (None, None) or len(seq)==0:
            self.set_linestyle('-')
        else:
            self.set_linestyle('--')
        self._dashSeq = seq  # TODO: offset ignored for now


    def _draw_lines(self, renderer, gc, path, trans):
        self._lineFunc(renderer, gc, path, trans)


    def _draw_steps_pre(self, renderer, gc, path, trans):
        vertices = self._xy
        steps = ma.zeros((2*len(vertices)-1, 2), np.float_)

        steps[0::2, 0], steps[1::2, 0] = vertices[:, 0], vertices[:-1, 0]
        steps[0::2, 1], steps[1:-1:2, 1] = vertices[:, 1], vertices[1:, 1]

        path = Path(steps)
        path = path.transformed(self.get_transform())
        self._lineFunc(renderer, gc, path, IdentityTransform())


    def _draw_steps_post(self, renderer, gc, path, trans):
        vertices = self._xy
        steps = ma.zeros((2*len(vertices)-1, 2), np.float_)

        steps[::2, 0], steps[1:-1:2, 0] = vertices[:, 0], vertices[1:, 0]
        steps[0::2, 1], steps[1::2, 1] = vertices[:, 1], vertices[:-1, 1]

        path = Path(steps)
        path = path.transformed(self.get_transform())
        self._lineFunc(renderer, gc, path, IdentityTransform())


    def _draw_steps_mid(self, renderer, gc, path, trans):
        vertices = self._xy
        steps = ma.zeros((2*len(vertices), 2), np.float_)

        steps[1:-1:2, 0] = 0.5 * (vertices[:-1, 0] + vertices[1:, 0])
        steps[2::2, 0] = 0.5 * (vertices[:-1, 0] + vertices[1:, 0])
        steps[0, 0] = vertices[0, 0]
        steps[-1, 0] = vertices[-1, 0]
        steps[0::2, 1], steps[1::2, 1] = vertices[:, 1], vertices[:, 1]

        path = Path(steps)
        path = path.transformed(self.get_transform())
        self._lineFunc(renderer, gc, path, IdentityTransform())


    def _draw_solid(self, renderer, gc, path, trans):
        gc.set_linestyle('solid')
        renderer.draw_path(gc, path, trans)


    def _draw_dashed(self, renderer, gc, path, trans):
        gc.set_linestyle('dashed')
        if self._dashSeq is not None:
            gc.set_dashes(0, self._dashSeq)

        renderer.draw_path(gc, path, trans)


    def _draw_dash_dot(self, renderer, gc, path, trans):
        gc.set_linestyle('dashdot')
        renderer.draw_path(gc, path, trans)


    def _draw_dotted(self, renderer, gc, path, trans):
        gc.set_linestyle('dotted')
        renderer.draw_path(gc, path, trans)


    def update_from(self, other):
        'copy properties from other to self'
        Artist.update_from(self, other)
        self._linestyle = other._linestyle
        self._linewidth = other._linewidth
        self._color = other._color
        self._markersize = other._markersize
        self._markerfacecolor = other._markerfacecolor
        self._markerfacecoloralt = other._markerfacecoloralt
        self._markeredgecolor = other._markeredgecolor
        self._markeredgewidth = other._markeredgewidth
        self._dashSeq = other._dashSeq
        self._dashcapstyle = other._dashcapstyle
        self._dashjoinstyle = other._dashjoinstyle
        self._solidcapstyle = other._solidcapstyle
        self._solidjoinstyle = other._solidjoinstyle

        self._linestyle = other._linestyle
        self._marker = MarkerStyle(other._marker.get_marker(),
                                   other._marker.get_fillstyle())
        self._drawstyle = other._drawstyle


    def _get_rgb_face(self, alt=False):
        facecolor = self._get_markerfacecolor(alt=alt)
        if is_string_like(facecolor) and facecolor.lower()=='none':
            rgbFace = None
        else:
            rgbFace = colorConverter.to_rgb(facecolor)
        return rgbFace

    # some aliases....
    def set_aa(self, val):
        'alias for set_antialiased'
        self.set_antialiased(val)

    def set_c(self, val):
        'alias for set_color'
        self.set_color(val)


    def set_ls(self, val):
        'alias for set_linestyle'
        self.set_linestyle(val)


    def set_lw(self, val):
        'alias for set_linewidth'
        self.set_linewidth(val)


    def set_mec(self, val):
        'alias for set_markeredgecolor'
        self.set_markeredgecolor(val)


    def set_mew(self, val):
        'alias for set_markeredgewidth'
        self.set_markeredgewidth(val)


    def set_mfc(self, val):
        'alias for set_markerfacecolor'
        self.set_markerfacecolor(val)

    def set_mfcalt(self, val):
        'alias for set_markerfacecoloralt'
        self.set_markerfacecoloralt(val)

    def set_ms(self, val):
        'alias for set_markersize'
        self.set_markersize(val)

    def get_aa(self):
        'alias for get_antialiased'
        return self.get_antialiased()

    def get_c(self):
        'alias for get_color'
        return self.get_color()


    def get_ls(self):
        'alias for get_linestyle'
        return self.get_linestyle()


    def get_lw(self):
        'alias for get_linewidth'
        return self.get_linewidth()


    def get_mec(self):
        'alias for get_markeredgecolor'
        return self.get_markeredgecolor()


    def get_mew(self):
        'alias for get_markeredgewidth'
        return self.get_markeredgewidth()


    def get_mfc(self):
        'alias for get_markerfacecolor'
        return self.get_markerfacecolor()

    def get_mfcalt(self, alt=False):
        'alias for get_markerfacecoloralt'
        return self.get_markerfacecoloralt()

    def get_ms(self):
        'alias for get_markersize'
        return self.get_markersize()

    def set_dash_joinstyle(self, s):
        """
        Set the join style for dashed linestyles
        ACCEPTS: ['miter' | 'round' | 'bevel']
        """
        s = s.lower()
        if s not in self.validJoin:
            raise ValueError('set_dash_joinstyle passed "%s";\n' % (s,)
                  + 'valid joinstyles are %s' % (self.validJoin,))
        self._dashjoinstyle = s

    def set_solid_joinstyle(self, s):
        """
        Set the join style for solid linestyles
        ACCEPTS: ['miter' | 'round' | 'bevel']
        """
        s = s.lower()
        if s not in self.validJoin:
            raise ValueError('set_solid_joinstyle passed "%s";\n' % (s,)
                  + 'valid joinstyles are %s' % (self.validJoin,))
        self._solidjoinstyle = s


    def get_dash_joinstyle(self):
        """
        Get the join style for dashed linestyles
        """
        return self._dashjoinstyle

    def get_solid_joinstyle(self):
        """
        Get the join style for solid linestyles
        """
        return self._solidjoinstyle

    def set_dash_capstyle(self, s):
        """
        Set the cap style for dashed linestyles

        ACCEPTS: ['butt' | 'round' | 'projecting']
        """
        s = s.lower()
        if s not in self.validCap:
            raise ValueError('set_dash_capstyle passed "%s";\n' % (s,)
                  + 'valid capstyles are %s' % (self.validCap,))

        self._dashcapstyle = s


    def set_solid_capstyle(self, s):
        """
        Set the cap style for solid linestyles

        ACCEPTS: ['butt' | 'round' |  'projecting']
        """
        s = s.lower()
        if s not in self.validCap:
            raise ValueError('set_solid_capstyle passed "%s";\n' % (s,)
                  + 'valid capstyles are %s' % (self.validCap,))

        self._solidcapstyle = s


    def get_dash_capstyle(self):
        """
        Get the cap style for dashed linestyles
        """
        return self._dashcapstyle


    def get_solid_capstyle(self):
        """
        Get the cap style for solid linestyles
        """
        return self._solidcapstyle


    def is_dashed(self):
        'return True if line is dashstyle'
        return self._linestyle in ('--', '-.', ':')

class VertexSelector:
    """
    Manage the callbacks to maintain a list of selected vertices for
    :class:`matplotlib.lines.Line2D`. Derived classes should override
    :meth:`~matplotlib.lines.VertexSelector.process_selected` to do
    something with the picks.

    Here is an example which highlights the selected verts with red
    circles::

        import numpy as np
        import matplotlib.pyplot as plt
        import matplotlib.lines as lines

        class HighlightSelected(lines.VertexSelector):
            def __init__(self, line, fmt='ro', **kwargs):
                lines.VertexSelector.__init__(self, line)
                self.markers, = self.axes.plot([], [], fmt, **kwargs)

            def process_selected(self, ind, xs, ys):
                self.markers.set_data(xs, ys)
                self.canvas.draw()

        fig = plt.figure()
        ax = fig.add_subplot(111)
        x, y = np.random.rand(2, 30)
        line, = ax.plot(x, y, 'bs-', picker=5)

        selector = HighlightSelected(line)
        plt.show()

    """
    def __init__(self, line):
        """
        Initialize the class with a :class:`matplotlib.lines.Line2D`
        instance.  The line should already be added to some
        :class:`matplotlib.axes.Axes` instance and should have the
        picker property set.
        """
        if not hasattr(line, 'axes'):
            raise RuntimeError('You must first add the line to the Axes')

        if line.get_picker() is None:
            raise RuntimeError('You must first set the picker property of the line')

        self.axes = line.axes
        self.line = line
        self.canvas = self.axes.figure.canvas
        self.cid = self.canvas.mpl_connect('pick_event', self.onpick)

        self.ind = set()


    def process_selected(self, ind, xs, ys):
        """
        Default "do nothing" implementation of the
        :meth:`process_selected` method.

        *ind* are the indices of the selected vertices.  *xs* and *ys*
        are the coordinates of the selected vertices.
        """
        pass

    def onpick(self, event):
        'When the line is picked, update the set of selected indicies.'
        if event.artist is not self.line: return

        for i in event.ind:
            if i in self.ind:
                self.ind.remove(i)
            else:
                self.ind.add(i)


        ind = list(self.ind)
        ind.sort()
        xdata, ydata = self.line.get_data()
        self.process_selected(ind, xdata[ind], ydata[ind])

lineStyles = Line2D._lineStyles
lineMarkers = MarkerStyle.markers
drawStyles = Line2D.drawStyles
fillStyles = MarkerStyle.fillstyles

docstring.interpd.update(Line2D = artist.kwdoc(Line2D))

# You can not set the docstring of an instancemethod,
# but you can on the underlying function.  Go figure.
docstring.dedent_interpd(Line2D.__init__.im_func)
