Change to aspect ratio handling

command. If there is any sentiment that it would be

    > desirable, I could make legend understand it as well, for
    > the sake of consistency.

I'm +1 on this. It only inconveniences power users.

    > I factored the plot box resizing and repositioning code out
    > into a PBox class ("Position Box", subclassed from list),
    > presently in its own pbox.py file, because I think it will
    > be more generally useful; the colorbar command is one such
    > intended use. It may be helpful to make Axes._position a
    > PBox instance instead of an ordinary list.

PBox appears to overlap matplotlib._transforms.BBox. Granted, the
mostly universal consensus is that mpl transforms are SNAFU-d (but
mostly work!), but is this increasing confusion by duplicating
functionality? In any case, you might consider simply extending BBox
(even if in python rather than extension code) or adding PBox to
transforms, cbook, or mlab, and describing what it does and how it is
different from BBox. It probably does not merit a separate module.

    > I think that aspect-ratio handling is now reasonably solid
    > and complete; I have tested it with pan and zoom, with and

For the record, this is a major and non-trivial step. I certainly
threw up my hands at attempting to fix it more than a year ago after
in-depth discussions with Perry about what was involved in making this
work in the presence of figure resizing, axes data limits, image
aspect ratios, etc. Thanks Eric and Mark for months of hard work.
This, plus prototype 3D, almost made me want to push for 1.0 :-).
Until, of course, I realized how much work still needs to be done on
the basics (Axis line handling and easy installation come to mind)

    > without ganged axes. I had to put in a couple of hacks in
    > backend_bases to prevent exceptions. (More careful analysis

Could you summarize these hacks here so we can look over them?

    > Thank you, Mark, for the original work that made the changes
    > possible.

Ditto.

JDH

John,

John Hunter wrote:

"Eric" == Eric Firing <efiring@...229...> writes:

    > command. If there is any sentiment that it would be
    > desirable, I could make legend understand it as well, for
    > the sake of consistency.

I'm +1 on this. It only inconveniences power users.

OK, I will put this on my mental list, but not at the top.

    > I factored the plot box resizing and repositioning code out
    > into a PBox class ("Position Box", subclassed from list),
    > presently in its own pbox.py file, because I think it will
    > be more generally useful; the colorbar command is one such
    > intended use. It may be helpful to make Axes._position a
    > PBox instance instead of an ordinary list.

PBox appears to overlap matplotlib._transforms.BBox. Granted, the
mostly universal consensus is that mpl transforms are SNAFU-d (but
mostly work!), but is this increasing confusion by duplicating
functionality? In any case, you might consider simply extending BBox
(even if in python rather than extension code) or adding PBox to
transforms, cbook, or mlab, and describing what it does and how it is
different from BBox. It probably does not merit a separate module.

I agree, and have moved it to transforms and added documentation. I originally considered putting it in transforms, but I decided to leave it separate initially, since it is not integrated with transforms. I also thought a little bit about integrating it, but expediency won out--I wanted to make something work first, and figure out the finer points, implications, and integrations later.

The topic of how to reduce overlap while increasing readability and/or power is open.

    > I think that aspect-ratio handling is now reasonably solid
    > and complete; I have tested it with pan and zoom, with and

As usual, I have found some more things that needed to be fixed. I have made those changes, and as you requested in another message I have moved the axis command functionality to Axes.axis, so pylab.axis is a thin wrapper.

For the record, this is a major and non-trivial step. I certainly
threw up my hands at attempting to fix it more than a year ago after
in-depth discussions with Perry about what was involved in making this
work in the presence of figure resizing, axes data limits, image
aspect ratios, etc. Thanks Eric and Mark for months of hard work.
This, plus prototype 3D, almost made me want to push for 1.0 :-).
Until, of course, I realized how much work still needs to be done on
the basics (Axis line handling and easy installation come to mind)

    > without ganged axes. I had to put in a couple of hacks in
    > backend_bases to prevent exceptions. (More careful analysis

Could you summarize these hacks here so we can look over them?

The basic problem is that if axes.set_xlim or axes.set_ylim get identical values, so that the data span of an axis is zero, it can cause trouble in several places. Furthermore, it seems that ValueError can be raised in the transform module when the data span is very small, not just when it is zero. I first tried to solve the problem by adding a little bit of code to set_xlim and set_ylim to prevent such singular and near-singular values. For some reason, it did not solve all the problems; I never figured out why. (In retrospect, perhaps I just did not use a large enough threshold.) The problems all seemed to be triggered by the button3 drag in zoom/pan, with axis('image'), so I put most of the block into a try/except structure, and I added singularity-checking to the arguments passed to set_xlim and set_ylim. The relevant extract from backend_bases is below, with pointers added:

             elif self._button_pressed==3:
                 try: # <-------
                     dx=(lastx-event.x)/float(a.bbox.width())
                     dy=(lasty-event.y)/float(a.bbox.height())
                     dx,dy=format_deltas(event,dx,dy)
                     alphax = pow(10.0,dx)
                     alphay = pow(10.0,dy)#use logscaling, avoid singularities and smother scaling...
                     lastx, lasty = trans.inverse_xy_tup( (lastx, lasty) )
                     if a.get_xscale()=='log':
                         xmin = lastx*(xmin/lastx)**alphax
                         xmax = lastx*(xmax/lastx)**alphax
                     else:
                         xmin = lastx+alphax*(xmin-lastx)
                         xmax = lastx+alphax*(xmax-lastx)
                     if a.get_yscale()=='log':
                         ymin = lasty*(ymin/lasty)**alphay
                         ymax = lasty*(ymax/lasty)**alphay
                     else:
                         ymin = lasty+alphay*(ymin-lasty)
                         ymax = lasty+alphay*(ymax-lasty)
                 except OverflowError: # <---------
                     warnings.warn('Overflow while panning')
                     return
             a.set_xlim(self.nonsingular(xmin, xmax)) # <--
             a.set_ylim(self.nonsingular(ymin, ymax)) # <--

         self.dynamic_update()

     def nonsingular(self, x0, x1): # <=================
         '''Desperate hack to prevent crashes when button-3 panning with
         axis('image') in effect.
         '''
         d = x1 - x0
         # much smaller thresholds seem to cause Value Error
         # later in Transformation::freeze in axes.draw()
         if abs(d) < 1e-10:
             warnings.warn('Axis data limit is too small for panning')
             x1 += 1e-10
             x0 -= 1e-10
         return (x0, x1)

What I don't understand at all is why this threshold (e.g., 1e-15 is too small) is needed to prevent problems in the freeze method.

I think the try/except is reasonable, and some sort of singularity-prevention is also reasonable, preferably inside set_xlim and set_ylim; but I am not comfortable with this high an arbitrary threshold.

Eric