errorbar cap color

howdy, I'm doing an errorbar plot. The cap colors are

    > always black, even when ecolor is explicitly set. The
    > documentation states that the second argument returned by
    > errorbar is a list of "error bar lines". I looked at the
    > return value and it appears to be a list of 3-tuples of
    > Line2D instances. I presume each 3-tuple is the errorbar,
    > and the two endcaps. If I explicitly set the color of the
    > whole erno collection, only the errorbar color is changed
    > not the cap color. I can't find any "errorbar Line2D" class
    > or documentation to that effect.

As Gary noted, recently we changed the way error bars were drawn which
apparently had some unintended consequences. The root of the problem
is related to one that came up earlier on the list about differences
in the way regular lines and marker lines are handled.

A little background. The Line2D class makes a distinction between
markers and lines: the color of markers, eg circles and squares, are
determined by the 'markeredgecolor' and 'markerfacecolor' properties;
the color of regular lines is determined by the 'color' property.
There is another special thing about markers: their sizes are in
points, and so do not depend on the axes scale or transformation. If
you zoom in on a marker, it still looks the same.

For errorbars, the last property is a nice, because we want the error
bar caps to be a fixed capsize in points rather than depend on the
scale. So we changed errorbar to use the new line *marker* symbols
'_' and '|'. While they plot like a solid line, matplotlib considers
them to be markers. So

  >> plot(x,y,'_')

are horizontal lines centered at x, with height y, and length
*in points* determined by the markersize. Ditto for '|, which are
vertical lines. Perfect for tick marks or errorbar caps.

However, as markers, their color is determined by the markerfacecolor
and markeredgecolor, which is a bit whacked, I realize. Perhaps they
should be factored into a separate category, or perhaps I should just
change the Line2D class to forward calls from set_color to
set_facecolor and set_edgecolor for these special markers.

I rewrote (and simplified) the errorbar code to fix this problem. It
should be faster too, because I plot the x and y caps in single calls
to plot rather than creating a bunch of individual lines, which was
how the '_' and '|' markers were meant to be used. Code below.

    > So what am I missing and how do I change errobar cap colors
    > without doing an explicit loop over the 3-tuples and
    > setting their colors explicitly?

For future reference, 'set' will loop for you

   >>> lines, errbars = errorbar(t, s, [e,g], f, fmt='o')
   >>> set(errbars, markerfacecolor='g', markeredgecolor='g')

but with the patched code, that won't be necessary.

Replace matplotlib.axes.Axes.errorbar with the code below. Errorbar
users, I would be much obliged if you test this. I tried all of
Gary's errorbar_demo examples and they appeared to work fine....

    def errorbar(self, x, y, yerr=None, xerr=None,
                 fmt='b-', ecolor='k', capsize=3):
        """
        Plot x versus y with error deltas in yerr and xerr.
        Vertical errorbars are plotted if yerr is not None
        Horizontal errorbars are plotted if xerr is not None

        xerr and yerr may be any of:
            a rank-0, Nx1 Numpy array - symmetric errorbars +/- value
            an N-element list or tuple - symmetric errorbars +/- value
            a rank-1, Nx2 Numpy array - asymmetric errorbars -column1/+column2

        Alternatively, x, y, xerr, and yerr can all be scalars, which
        plots a single error bar at x, y.
        
        fmt is the plot format symbol for y. if fmt is None, just
        plot the errorbars with no line symbols. This can be useful
        for creating a bar plot with errorbars

        Return value is a length 2 tuple. The first element is a list of
        y symbol lines. The second element is a list of error bar lines.

        capsize is the size of the error bar caps in points
        """
        if not self._hold: self.cla()
        # make sure all the args are iterable arrays
        if not iterable(x): x = asarray([x])
        else: x = asarray(x)

        if not iterable(y): y = asarray([y])
        else: y = asarray(y)

        if xerr is not None:
            if not iterable(xerr): xerr = asarray([xerr])
            else: xerr = asarray(xerr)

        if yerr is not None:
            if not iterable(yerr): yerr = asarray([yerr])
            else: yerr = asarray(yerr)

        if fmt is not None:
            l0 = self.plot(x,y,fmt)
        else: l0 = None
        caplines = []
        barlines = []
        
        capargs = {'c':ecolor, 'mfc':ecolor, 'mec':ecolor, 'ms':2*capsize}

        if xerr is not None:
            if len(xerr.shape) == 1:
                left = x-xerr
                right = x+xerr
            else:
                left = x-xerr[0]
                right = x+xerr[1]

            barlines.extend( self.hlines(y, x, left) )
            barlines.extend( self.hlines(y, x, right) )
            caplines.extend(self.plot(left, y, '|', **capargs))
            caplines.extend(self.plot(right, y, '|', **capargs))

        if yerr is not None:
            if len(yerr.shape) == 1:
                lower = y-yerr
                upper = y+yerr
            else:
                lower = y-yerr[0]
                upper = y+yerr[1]

            barlines.extend( self.vlines(x, y, upper ) )
            barlines.extend( self.vlines(x, y, lower ) )

            caplines.extend(self.plot(x, lower, '_', **capargs))
            caplines.extend(self.plot(x, upper, '_', **capargs))

        for l in barlines:
            l.set_color(ecolor)

        self.autoscale_view()

        return (l0, caplines+barlines)