Difference in Axes.hlines / Axes.vlines

Hi,

There is a difference in the behaviour of Axes.hlines() vs
Axes.vlines(), in that vlines() lets you supply scalars for ymin and
ymax, whereas hlines() doesn't (for xmin and xmax). The patch below
fixes that, and also what looks like a separate bug in vlines. There
are also other differences, around units, but I haven't worked with that
area of matplotlib so will leave that for others.

I was wondering, though, whether there'd be any support for some work
which tidied up the near-duplicate code in axes.py. I've been playing
around with an approach using python's metaclass support, which, for
example, would replace the definitions of the two near-identical
functions set_xscale() and set_yscale() with the one meta-definition:

    @MC_Traited.construct_traited_variants
    def set__AXISLETTER_scale(self, value, base_AXISLETTER_ = 10, subs_AXISLETTER_ = None):
        """
        SET_%(axis_letter_UC)sSCALE(value, base%(axis_letter)s = 10, subs%(axis_letter)s = None)

        Set the %(axis_letter)s-scaling: 'log' or 'linear'

        If value is 'log', the additional kwargs have the following meaning

            * base%(axis_letter)s: base of the logarithm

            * subs%(axis_letter)s: a sequence of the location of the minor ticks;
              None defaults to autosubs, which depend on the number of
              decades in the plot. Eg for base 10, subs%(axis_letter)s = (1, 2, 5) will
              put minor ticks on 1, 2, 5, 11, 12, 15, 21, ....
              To turn off minor ticking, set subs%(axis_letter)s = []

        ACCEPTS: ['log' | 'linear']
        """
        assert(value.lower() in ('log', 'linear'))
        my_axis = _TRAITS_.my_axis(self)
        if value == 'log':
            my_axis.set_major_locator(LogLocator(base_AXISLETTER_))
            my_axis.set_major_formatter(LogFormatterMathtext(base_AXISLETTER_))
            my_axis.set_minor_locator(LogLocator(base_AXISLETTER_, subs_AXISLETTER_))
            _TRAITS_.get_my_func(self.transData).set_type(LOG10)
            minval, maxval = _TRAITS_.get_my_lim(self)
            if min(minval, maxval) <= 0:
                self.autoscale_view()
        elif value == 'linear':
            my_axis.set_major_locator(AutoLocator())
            my_axis.set_major_formatter(ScalarFormatter())
            my_axis.set_minor_locator(NullLocator())
            my_axis.set_minor_formatter(NullFormatter())
            _TRAITS_.get_my_func(self.transData).set_type(IDENTITY)

I haven't quite worked through the details, but it looks like it would
cut nearly 400 lines off axes.py (while adding c.200 lines of supporting
code), as well as avoid the type of inconsistency seen in hlines/vlines.
If there's interest, I'll post what I've got so far for comments.

Thanks,

Ben.

- - - - 8< - - - -

--- ORIG/axes.py 2007-07-17 15:24:10.367402000 +0100
+++ NEW/axes.py 2007-07-17 15:38:03.705394000 +0100
@@ -2325,6 +2325,11 @@
         xmin = asarray(xmin)
         xmax = asarray(xmax)

+ if len(xmin)==1:
+ xmin = xmin*ones(y.shape, typecode(y))
+ if len(ymax)==1:
+ xmax = xmax*ones(y.shape, typecode(y))

···

+

         if len(xmin)!=len(y):
             raise ValueError, 'xmin and y are unequal sized sequences'
@@ -2418,7 +2423,7 @@
         minx = nx.amin(x)
         maxx = nx.amax(x)
         miny = min(nx.amin(ymin), nx.amin(ymax))
- maxy = max(nx.amax(ymax), nx.amax(ymax))
+ maxy = max(nx.amax(ymin), nx.amax(ymax))
         minx, maxx = self.convert_xunits((minx, maxx))
         miny, maxy = self.convert_yunits((miny, maxy))
         corners = (minx, miny), (maxx, maxy)

There is a difference in the behaviour of Axes.hlines() vs
Axes.vlines(), in that vlines() lets you supply scalars for ymin and
ymax, whereas hlines() doesn't (for xmin and xmax). The patch below
fixes that, and also what looks like a separate bug in vlines. There
are also other differences, around units, but I haven't worked with that
area of matplotlib so will leave that for others.

Thanks for catching that -- I recently introduced this bug when I
numpified the axes module. Fixed in svn

I was wondering, though, whether there'd be any support for some work
which tidied up the near-duplicate code in axes.py. I've been playing

Certainly, but probably not using meta-classes.

around with an approach using python's metaclass support, which, for
example, would replace the definitions of the two near-identical
functions set_xscale() and set_yscale() with the one meta-definition:

    @MC_Traited.construct_traited_variants
    def set__AXISLETTER_scale(self, value, base_AXISLETTER_ = 10, subs_AXISLETTER_ = None):
        """
        SET_%(axis_letter_UC)sSCALE(value, base%(axis_letter)s = 10, subs%(axis_letter)s = None)

I'm disinclined to use python black magic -- I find it makes the code
harder to grok for the typical scientist, and these are our main
developers. Most of these guys are still trying to figure out what a
class is <wink>

But there are other ways to reduce code duplication that are not as clever

def set_xscale(self, value, basex = 10, subsx=None):
    self._set_scale(axis=self.xaxis,
                    transfunc=self.transData.get_funcx(),
                    limfunc=self.get_xlim,
                    value=value,
                    base=basex,
                    subse=subsx)

def set_yscale(self, value, basex = 10, subsx=None):
    self._set_scale(axis=self.yaxis,
                    transfunc=self.transData.get_funcy(),
                    limfunc=self.get_ylim,
                    value=value,
                    base=basey,
                    subse=subsy)

def _set_scale(self, axis, transfunc, limfunc, value, basex = 10, subsx=None):
    assert(value.lower() in ('log', 'linear', ))
    if value == 'log':
        axis.set_major_locator(mticker.LogLocator(base))
        axis.set_major_formatter(mticker.LogFormatterMathtext(base))
        axis.set_minor_locator(mticker.LogLocator(base,subsx))
        transfunc.set_type(mtrans.LOG10)
        minx, maxx = limfunc()
        if min(minx, maxx)<=0:
            self.autoscale_view()
    elif value == 'linear':
        axis.set_major_locator(mticker.AutoLocator())
        axis.set_major_formatter(mticker.ScalarFormatter())
        axis.set_minor_locator(mticker.NullLocator())
        axis.set_minor_formatter(mticker.NullFormatter())
        transfunc.set_type( mtrans.IDENTITY )

which I definitely encourage. There may be something more elegant
than this, but we only try to be a little clever. Get too clever, and
Darren and Eric will yell at you.

···

On 7/17/07, Ben North <ben@...1679...> wrote:

It's true. That bit of code looks like Perl to me.

Darren

···

On Tuesday 17 July 2007 12:04:54 pm John Hunter wrote:

> I was wondering, though, whether there'd be any support for some work
> which tidied up the near-duplicate code in axes.py. I've been playing

Certainly, but probably not using meta-classes.

> around with an approach using python's metaclass support, which, for
> example, would replace the definitions of the two near-identical
> functions set_xscale() and set_yscale() with the one meta-definition:
>
> @MC_Traited.construct_traited_variants
> def set__AXISLETTER_scale(self, value, base_AXISLETTER_ = 10,
> subs_AXISLETTER_ = None): """
> SET_%(axis_letter_UC)sSCALE(value, base%(axis_letter)s = 10,
> subs%(axis_letter)s = None)

I'm disinclined to use python black magic -- I find it makes the code
harder to grok for the typical scientist, and these are our main
developers. Most of these guys are still trying to figure out what a
class is <wink>