change in autoscale_view() behavior?

Glen W. Mabey wrote:

I'm in the process of migrating some software I've written from a
machine with MPL 0.86.2 on it to another with 0.87.2 .

The app is Qt-based with MPL embedded as a widget. The plot itself uses
dates on the x-axis.

It appears that the behavior of autoscale_view() changed quite a bit
somewhere between these two versions.

In my application I create a patches.Rectangle that is 100 seconds wide:

    start_time_num = matplotlib.dates.date2num( start_time )
    lower_left = [ matplotlib.dates.date2num( start_time ), start_y ]
    width = matplotlib.dates.date2num( start_time + datetime.timedelta( seconds=10 ) ) - start_time_num
    r = matplotlib.patches.Rectangle( lower_left, width, height )

With the 0.86.2 version, the total span of the x-axis after an
autoscale_view() call is about 12 seconds. For the 0.87.2 version
however, the span is more than 200 years. (see attached .png files)

I don't see this type of behavior in the pylab environment, yet I'm
having a hard time creating a concise example that demonstrates the
behavior.

I can keep working on extracting an snippet, but I thought I'd ask
if this is some intended behavioral change, and if so, if there is a
workaround.

Okay, I figured out what the critical parameter is: if the width of the
Rectangle is less than one-millionth of the x value, then I get the
centuries of timespan on the x-axis phenomenon.

(That's probably blindingly obvious in the source code ... somewhere.)

So, first of all, this behavior has nothing to do with the OO interface,
nor the Qt backend.

This seems like reasonable behavior for autoscaling in general.
Certainly you can't zoom in nicely around a patch with zero width;
you have to draw the line somewhere. (ha ha, no pun intended)

It just so happens that for my application, I need to plot Rectangles
that represent events as a function of time and frequency. As such, my
x-values (as produced by date2num()) are on the order of 732421, yet
I need to display patches that are like 10 seconds wide, which turns
out to be 0.000115740695037, after a date2num() conversion.

This behavior is exhibited in the following lines of code. A delta of
65000 seconds is sufficient to get the scaling right.

···

#########

import matplotlib
import matplotlib.dates
from matplotlib.pylab import *

import datetime

ax = subplot(111)

start_time = datetime.datetime(2006, 4, 21)
start_time_num = matplotlib.dates.date2num( start_time )

lower_left = [ start_time_num, 1.345680002 ]
width = matplotlib.dates.date2num( start_time + datetime.timedelta( seconds=100 ) ) - start_time_num
print 'width = ', width

ax.add_patch( matplotlib.patches.Rectangle( lower_left, width, 2 ) )

ax.autoscale_view()
ax.xaxis.set_major_formatter( matplotlib.dates.DateFormatter('%Y') )

print ax.get_xlim()
show()

##########

So, this seems like a fairly narrow case where the threshold in use
causes a problem. I can't imagine another instance where 1e-6 would not
be prefectly fine.

One solution would be to just patch the source for my deployment of this
software ...

I hesitate to suggest that this level ought to be globally tunable by
the user, although an additional parameter that could be passed to
autoscale_view() wouldn't be as bad. Still that seems like clutter.

What say ye?

Thank you,
Glen Mabey

Glen,

The change you ran into is mine. The relevant parameter is in this method of the base Locator class:

     def nonsingular(self, vmin, vmax, expander=0.001, tiny=1e-6):
         if vmax < vmin:
             vmin, vmax = vmax, vmin
         if vmax - vmin <= max(abs(vmin), abs(vmax)) * tiny:
             if vmin==0.0:
                 vmin -= 1
                 vmax += 1
             else:
                 vmin -= expander*abs(vmin)
                 vmax += expander*abs(vmax)
         return vmin, vmax

This method gets called in several places. Some of them supply the expander kwarg, but I don't think any supply the tiny kwarg, so the default, which I arbitrarily set to 1e-6, is used. I dimly recall finding that using a *much* smaller value could cause trouble elsewhere.

Rather than patching the code you send out, I suggest you consider using a custom Locator, which would allow you to set this parameter to whatever works for you, and maybe put in additional tweaks for your unusual use case. You could subclass whatever Locator you are using now, e.g.

class myLocator(MaxNLocator):
     def nonsingular(self, vmin, vmax, expander=0.001, tiny=1e-12):
         return Locator.nonsingular(self, vmin, vmax,
                                    expander=expander, tiny=tiny)
and then

ax.xaxis.set_major_locator(myLocator())

This is untested, but I think something along these lines is all you would need. Everything except nonsingular would be inherited, and nonsingular would be called with your new default value for tiny.

As an alternative approach, can you simply use as your x-coordinate time deltas from a suitable base time, so that vmin and vmax would not be so huge compared to their difference? My assumption while working with the ticker.py code was that this would be the logical solution to this sort of problem.

Longer term, it would be easy to modify the Locator classes so that the default values of tiny and expander would appear as a kwargs in __init__. Whether this would be a valuable change or mere clutter, I don't know; I will listen for more advice.

Eric

Glen W. Mabey wrote:

···

Glen W. Mabey wrote:

I'm in the process of migrating some software I've written from a
machine with MPL 0.86.2 on it to another with 0.87.2 .

The app is Qt-based with MPL embedded as a widget. The plot itself uses
dates on the x-axis.

It appears that the behavior of autoscale_view() changed quite a bit
somewhere between these two versions.

In my application I create a patches.Rectangle that is 100 seconds wide:

   start_time_num = matplotlib.dates.date2num( start_time )
   lower_left = [ matplotlib.dates.date2num( start_time ), start_y ]
   width = matplotlib.dates.date2num( start_time + datetime.timedelta( seconds=10 ) ) - start_time_num
   r = matplotlib.patches.Rectangle( lower_left, width, height )

With the 0.86.2 version, the total span of the x-axis after an
autoscale_view() call is about 12 seconds. For the 0.87.2 version
however, the span is more than 200 years. (see attached .png files)

I don't see this type of behavior in the pylab environment, yet I'm
having a hard time creating a concise example that demonstrates the
behavior.

I can keep working on extracting an snippet, but I thought I'd ask
if this is some intended behavioral change, and if so, if there is a
workaround.

Okay, I figured out what the critical parameter is: if the width of the
Rectangle is less than one-millionth of the x value, then I get the centuries of timespan on the x-axis phenomenon.

(That's probably blindingly obvious in the source code ... somewhere.)

So, first of all, this behavior has nothing to do with the OO interface,
nor the Qt backend.

This seems like reasonable behavior for autoscaling in general.
Certainly you can't zoom in nicely around a patch with zero width;
you have to draw the line somewhere. (ha ha, no pun intended)

It just so happens that for my application, I need to plot Rectangles
that represent events as a function of time and frequency. As such, my
x-values (as produced by date2num()) are on the order of 732421, yet I need to display patches that are like 10 seconds wide, which turns out to be 0.000115740695037, after a date2num() conversion.

This behavior is exhibited in the following lines of code. A delta of 65000 seconds is sufficient to get the scaling right.

#########

import matplotlib
import matplotlib.dates
from matplotlib.pylab import *

import datetime

ax = subplot(111)

start_time = datetime.datetime(2006, 4, 21)
start_time_num = matplotlib.dates.date2num( start_time )

lower_left = [ start_time_num, 1.345680002 ]
width = matplotlib.dates.date2num( start_time + datetime.timedelta( seconds=100 ) ) - start_time_num
print 'width = ', width

ax.add_patch( matplotlib.patches.Rectangle( lower_left, width, 2 ) )

ax.autoscale_view()
ax.xaxis.set_major_formatter( matplotlib.dates.DateFormatter('%Y') )

print ax.get_xlim()
show()

##########

So, this seems like a fairly narrow case where the threshold in use
causes a problem. I can't imagine another instance where 1e-6 would not
be prefectly fine.

One solution would be to just patch the source for my deployment of this
software ...

I hesitate to suggest that this level ought to be globally tunable by
the user, although an additional parameter that could be passed to
autoscale_view() wouldn't be as bad. Still that seems like clutter.

What say ye?

Thank you,
Glen Mabey

-------------------------------------------------------
Using Tomcat but need to do more? Need to support web services, security?
Get stuff done quickly with pre-integrated technology to make your job easier
Download IBM WebSphere Application Server v.1.0.1 based on Apache Geronimo
http://sel.as-us.falkag.net/sel?cmd=lnk&kid=120709&bid=263057&dat=121642
_______________________________________________
Matplotlib-users mailing list
Matplotlib-users@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/matplotlib-users

class myLocator(MaxNLocator):
    def nonsingular(self, vmin, vmax, expander=0.001, tiny=1e-12):
        return Locator.nonsingular(self, vmin, vmax,
                                   expander=expander, tiny=tiny)
and then

ax.xaxis.set_major_locator(myLocator())

This worked out of the box. Thanks!

As an alternative approach, can you simply use as your x-coordinate time
deltas from a suitable base time, so that vmin and vmax would not be so
huge compared to their difference? My assumption while working with the
ticker.py code was that this would be the logical solution to this sort
of problem.

Well, the complication is that I not only need to be able to zoom in for
~10sec segments, but also to zoom out on long segments. So, yes, I could
do a modulus-year operation, but that would complicate labeling of the ticks
and also in getting data points back from click events. Doable, just a
little more complicated.

Longer term, it would be easy to modify the Locator classes so that the
default values of tiny and expander would appear as a kwargs in
__init__. Whether this would be a valuable change or mere clutter, I
don't know; I will listen for more advice.

If it's that simple, then I would vote for it. Surely someone will find
it useful someday. However, the work-around I've implemented isn't bad.

Thank you for your help.

Glen Mabey

···

On Wed, Apr 26, 2006 at 01:22:17PM -1000, Eric Firing wrote: