auto log and key press

I just committed some changes to CVS for auto-log scaling of line
plots - you pay a performance hit for log plots but it appears to
work. Eg, you can do

    x = arange(-2.002, 10, 0.01)
    y = sin(2*pi*x)
    plot(x,y)
    set(gca(), xscale='log')

and only the positive data are plotted.

If you have data points really close to 0, eg

    1 >>> x = arange(-2.00, 10, 0.01)

    2 >>> amin(abs(x))
    Out[2]: 4.163336342344337e-17

You may a heavy performance price because so many decades are plotted,
each with minor ticks, and ticks are expensive in the current impl.

I am also implementing some default keypress events on the pylab
figure manager canvas. Eg 'g' to toggle grid mode.

I would like a key binding for toggling log/linear scale for x and y
independently. 'x' and 'y' are not good choices since they are
overloaded with constraining axes in interactive pan/zoom with the
toolbar. Suggestions? '1' and '2'? CTRL-x and and CTRL-y? 'l' and
'L'?

Are there other keybindings people would like to see implemented in
the default pylab figures?

JDH

John Hunter wrote:

I just committed some changes to CVS for auto-log scaling of line
plots - you pay a performance hit for log plots but it appears to
work. Eg, you can do

    x = arange(-2.002, 10, 0.01)
    y = sin(2*pi*x)
    plot(x,y)
    set(gca(), xscale='log')

and only the positive data are plotted.

Hmm. Is it possible that this hasn't propagated to public CVS yet? I just updated, and this is what I get:

planck[mwadap]> pylab

In [1]: x = arange(-2.002, 10, 0.01)

In [2]: y = sin(2*pi*x)

In [3]: plot(x,y)
Out[3]: [<matplotlib.lines.Line2D instance at 0x41041f0c>]

In [4]: set(gca(), xscale='log')

···

---------------------------------------------------------------------------
exceptions.ValueError Traceback (most recent call last)

/home/fperez/research/code/mwadap/<console>

/usr/lib/python2.3/site-packages/matplotlib/pylab.py in set(h, *args, **kwargs)
    1230 raise RuntimeError(msg)
    1231
-> 1232 draw_if_interactive()
    1233 return [x for x in flatten(ret)]
    1234

/usr/local/home/fperez/code/python/IPython/genutils.py in wrapper(*args, **kw)

/usr/lib/python2.3/site-packages/matplotlib/backends/__init__.py in draw_if_interactive()
      40 def draw_if_interactive():
      41 draw_if_interactive._called = True
---> 42 __draw_int()
      43 # Flag to store state, so external callers (like ipython) can keep track
      44 # of draw calls.

/usr/lib/python2.3/site-packages/matplotlib/backends/backend_tkagg.py in draw_if_interactive()
      56 figManager = Gcf.get_active()
      57 if figManager is not None:
---> 58 figManager.show()
      59
      60

/usr/lib/python2.3/site-packages/matplotlib/backends/backend_tkagg.py in show(self)
     276 # anim.py requires this
     277 if sys.platform=='win32' : self.window.update()
--> 278 else: self.canvas.draw()
     279 self._shown = True
     280

/usr/lib/python2.3/site-packages/matplotlib/backends/backend_tkagg.py in draw(self)
     141
     142 def draw(self):
--> 143 FigureCanvasAgg.draw(self)
     144 tkagg.blit(self._tkphoto, self.renderer._renderer, 2)
     145 self._master.update_idletasks()

/usr/lib/python2.3/site-packages/matplotlib/backends/backend_agg.py in draw(self)
     310 self.renderer = RendererAgg(w, h, self.figure.dpi)
     311 self._lastKey = key
--> 312 self.figure.draw(self.renderer)
     313
     314 def tostring_rgb(self):

/usr/lib/python2.3/site-packages/matplotlib/figure.py in draw(self, renderer)
     336
     337 # render the axes
--> 338 for a in self.axes: a.draw(renderer)
     339
     340 # render the figure text

/usr/lib/python2.3/site-packages/matplotlib/axes.py in draw(self, renderer)
    1480 if not self.get_visible(): return
    1481 renderer.open_group('axes')
-> 1482 self.transData.freeze() # eval the lazy objects
    1483 self.transAxes.freeze() # eval the lazy objects
    1484 if self.axison:

ValueError: Cannot take log of nonpositive value

Note that I'm running straight off the CVS directory, because the RPM rebuild/reinstall takes too long for permanent testing. What I did was just to manually copy the .so files back into the CVS dir, renamed /usr/lib/python2.3/site-packages/matplotlib to .ori, and made a symlink:

planck[site-packages]> d matplotlib
/usr/lib/python2.3/site-packages
lrwxrwxrwx 1 root 51 Feb 4 11:18 matplotlib -> /usr/local/installers/src/matplotlib/lib/matplotlib/

This seems to work OK (I checked with a few print statements that I am indeed running off the CVS matplotlib/ dir). Since the most recent CVS update doesn't seem to change any C++ code, this should be OK, no?

I'm seeing further weirdness with log plots. Try this:

semilogy(frange(.1,1,npts=20),frange(0.1,1,npts=20))
semilogy(frange(.1,1,npts=20),frange(0.0,1,npts=20))
semilogy(frange(.1,1,npts=20),frange(0.1,1,npts=20))

Not only does the second one crash, but then, the third line (identical to the first) also crashes. Something is left in an internally inconsistent state, and essentially all log plots become impossible afterwards. The only solution is to restart pylab altogether.

If you have data points really close to 0, eg

    1 >>> x = arange(-2.00, 10, 0.01)

    2 >>> amin(abs(x))
    Out[2]: 4.163336342344337e-17

You may a heavy performance price because so many decades are plotted,
each with minor ticks, and ticks are expensive in the current impl.

I am also implementing some default keypress events on the pylab
figure manager canvas. Eg 'g' to toggle grid mode.

I would like a key binding for toggling log/linear scale for x and y
independently. 'x' and 'y' are not good choices since they are
overloaded with constraining axes in interactive pan/zoom with the
toolbar. Suggestions? '1' and '2'? CTRL-x and and CTRL-y? 'l' and
'L'?

I like 'l'/'L' for gnuplot consistency. Note that their implementation is a bit funky though: 'l' toggles y-axis log (the most common case), while 'L' toggles log on the axis closest to the mouse pointer. I'll leave it up to you to decide whether you like this mouse-proximity thing or not. But 'l' for y-log, which is probably the most common type of log plot, I think is nice.

Are there other keybindings people would like to see implemented in
the default pylab figures?

'r' for the ruler thingie?

Best,

f

John Hunter wrote:

If you have data points really close to 0, eg

    1 >>> x = arange(-2.00, 10, 0.01)

    2 >>> amin(abs(x))
    Out[2]: 4.163336342344337e-17

You may a heavy performance price because so many decades are plotted,
each with minor ticks, and ticks are expensive in the current impl.

I had a look at gnuplot's strategy:

planck[~]> npy

In [1]: x = frange(1e-40, 10, npts=1003)

In [2]: gp('set logscale y')

In [3]: plot x,filename='logplotex.eps'

The result is here:

http://amath.colorado.edu/faculty/fperez/tmp/logplotex.eps

They seem to plot a maximum of 11 major ticks (that's what I'm guessing from a bunch of tests). When it fits, each major tick is a decade, but at some point their algorithm switches over (like in my example) to every 3rd, 5th, whatever-th decade, and the minor ticks become decade ticks themselves. When this happens, there are no logarithmically spaced ticks any more, obviously.

This is overall a nice approach, I think. I have quite a few plots which cover 30 decades, and in matplotlib the result looks very crowded, while gnuplot's enforcement of a max of 11 (or whatever) major ticks gives a clean looking plot.

Gnuplot has many problems (hence my switch -finally- to mpl), but it has over a decade of fine-tuning of its behavior and interface, so it's not a bad source of inspiration. It is mature and robust, and many of the things it does, it does really well. I'll keep bringing up areas where I feel we can benefit from it (I know it reasonably well) as I move all my code over to mpl.

Cheers,

f

Fernando Perez wrote:

I would like a key binding for toggling log/linear scale for x and y
independently. 'x' and 'y' are not good choices since they are
overloaded with constraining axes in interactive pan/zoom with the
toolbar. Suggestions? '1' and '2'? CTRL-x and and CTRL-y? 'l' and
'L'?

I like 'l'/'L' for gnuplot consistency. Note that their implementation is a bit funky though: 'l' toggles y-axis log (the most common case), while 'L' toggles log on the axis closest to the mouse pointer. I'll leave it up to you to decide whether you like this mouse-proximity thing or not. But 'l' for y-log, which is probably the most common type of log plot, I think is nice.

On second thought, I am starting to like the mouse-proximity thingie: it allows you to point at a specific axis and set only that one, which can be very useful if you have a bunch of subplots and want to only change one specific axis. This, which I imagine would take some careful work at the command line, would be trivial to do if you could just put your mouse pointer over it and hit a key/button.

So now I'm +1 on following gnuplot's inspiration here.

As to which y-axis a plain 'l' should modify in the presence of subplots, I'm not sure. All? The first one? Is there a concept of 'active axis' in a plot with subplots? I simply don't know mpl enough to say anything useful here.

cheers,

f

John Hunter wrote:

I just committed some changes to CVS for auto-log scaling of line
plots - you pay a performance hit for log plots but it appears to
work. Eg, you can do

    x = arange(-2.002, 10, 0.01)
    y = sin(2*pi*x)
    plot(x,y)
    set(gca(), xscale='log')

and only the positive data are plotted.

OK, with ssh CVS this works quite well. If you try the same with

      set(gca(), yscale='log')

you'll see a funky junction. I think here gnuplot can again give us some guidance for good bailout behaviour:

http://amath.colorado.edu/faculty/fperez/tmp/log-sin.ps

I think this is a reasonable approach.

Now, there is something funky though in semilogy:

In [13]: plot(frange(.1,1,npts=20),frange(0.1,1,npts=20))
Out[13]: [<matplotlib.lines.Line2D instance at 0x40f631ac>]

In [14]: set(gca(), yscale='log')
Out[14]: [None]

Works perfectly. Yet:

In [15]: close('all')

In [16]: semilogy(frange(.1,1,npts=20),frange(0.1,1,npts=20))
ERROR: min() or max() arg is an empty sequence

I'd expect these two to be identical, no? Perhaps you just haven't had the time to track down all the places where this needs to be applied.

At any rate, this is a huge improvement for log plots (which I happen to use every day). You've pretty much bought yourself the %run backend work, and at least a stab at the gtk stuff for ipython :slight_smile:

Best,

f

ps. Now that I'm good with ssh CVS, let me know if you finish polishing this up, and I can test it quickly and report back. I have a ton of pretty stressful log plots I can throw at it.