New aspect function for pylab

Eric, list -

Here is the new aspect function for possible inclusion in pylab.
It works great and with the default values for the kwargs, it works exactly the way I think it is useful for a combination of contouring and plotting.

What do you think, should we include this?

Mark

def aspect(*args, **kwargs):
“”"
Set/Get the aspect ratio (ylength/xlength) of the current axis

Aspect ratio is defined as unit-length-along-y-axis / unit-length-along-x-axis

If no arguments are passed, the current aspect ratio is returned
Aspect ratio may be met by changing size of axes while keeping data limits fixed (called 'box'),
or by changing data limits while keeping the lengths of the axes fixed (called 'datalim')

One point remains fixed, which is called the anchor, for example the center (called 'C')
Autoscaling may be turned on (limits may change upon next plotting command)
or off (limits will remain fixed)

Keyword arguments:
adjustable: 'box' (default), 'datalim'
anchor: 'C' (default), 'SW', 'S', 'SE', 'E', 'NE', 'N', 'NW', 'W'

fixlim: True (default), False
"""

ax = gca()
if len(args)==0:
    aspect = ax.get_aspect()
elif len(args)==1:
    aspect = args[0]
adjustable = popd(kwargs,'adjustable','box')

anchor = popd(kwargs,'anchor','C')
fixlim = popd(kwargs,'fixlim',True)
ax.set_aspect(aspect,adjustable,anchor)
if fixlim:
    ax.set_autoscale_on(False)
else:

    ax.set_autoscale_on(True)
    ax.autoscale_view()
draw_if_interactive()
return aspect

Whenever you are mutating a kwargs dictionary, eg with popd, you
should first copy it. The user may be creating, saving, and resuing a
kwarg dict

  myaspectprops = dict(adjustable='box', anchor='NW')
  # some code
  aspect(**myaspectprops)
  # some more code
  aspect(**myaspectprops)

This will fail if you pop off of the kwargs in the aspect function w/o
first copying it. So always do

  kwargs = kwargs.copy()

before calling popd and friends.

This is covered in the "CODING_GUIDE" document in the svn repository.

JDH

···

On 2/26/07, Mark Bakker <markbak@...287...> wrote:

    ax = gca()
    if len(args)==0:
        aspect = ax.get_aspect()
    elif len(args)==1:
        aspect = args[0]
    adjustable = popd(kwargs,'adjustable','box')
    anchor = popd(kwargs,'anchor','C')
    fixlim = popd(kwargs,'fixlim',True)

Thanks for the explanation, John.
I printed out the CODING_GUIDE (sorry, didn’t know it existed).
The new function with the extra copy command is shown below.
Can we add this to pylab?
Thanks, Mark

def aspect(*args, **kwargs):
“”"
Set/Get the aspect ratio (ylength/xlength) of the current axis

Aspect ratio is defined as unit-length-along-y-axis / unit-length-along-x-axis
If no arguments are passed, the current aspect ratio is returned

Aspect ratio may be met by changing size of axes while keeping data limits fixed (called 'box'),
or by changing data limits while keeping the lengths of the axes fixed (called 'datalim')
One point remains fixed, which is called the anchor, for example the center (called 'C')
Autoscaling may be turned on (limits may change upon next plotting command)
or off (limits will remain fixed)

Keyword arguments:
adjustable: 'box' (default), 'datalim'
anchor: 'C' (default), 'SW', 'S', 'SE', 'E', 'NE', 'N', 'NW', 'W'

fixlim: True (default), False
"""

ax = gca()
if len(args)==0:
    aspect = ax.get_aspect()
elif len(args)==1:
    aspect = args[0]
kwargs = kwargs.copy

()
adjustable = popd(kwargs,‘adjustable’,‘box’)
anchor = popd(kwargs,‘anchor’,‘C’)
fixlim = popd(kwargs,‘fixlim’,True)
ax.set_aspect(aspect,adjustable,anchor)

if fixlim:
    ax.set_autoscale_on(False)
else:
    ax.set_autoscale_on(True)
    ax.autoscale_view()
draw_if_interactive()
return aspect
···

On 2/26/07, John Hunter <jdh2358@…287…> wrote:

On 2/26/07, Mark Bakker <markbak@…287…> wrote:

ax = gca()
if len(args)==0:
    aspect = ax.get_aspect()
elif len(args)==1:
    aspect = args[0]
adjustable = popd(kwargs,'adjustable','box')
anchor = popd(kwargs,'anchor','C')
fixlim = popd(kwargs,'fixlim',True)

Whenever you are mutating a kwargs dictionary, eg with popd, you
should first copy it. The user may be creating, saving, and resuing a
kwarg dict

myaspectprops = dict(adjustable=‘box’, anchor=‘NW’)

some code

aspect(**myaspectprops)

some more code

aspect(**myaspectprops)

This will fail if you pop off of the kwargs in the aspect function w/o
first copying it. So always do

kwargs = kwargs.copy()

before calling popd and friends.

This is covered in the “CODING_GUIDE” document in the svn repository.

JDH

Since Eric has been developing and maintaining the aspect stuff of
late, I'll leave it to him to review and contribute. My one comment
is I want to make pylab as thin a wrapper as possible and where
possible prevent it from doing anything useful. That is, I'd like to
see all the functionality in the API, and the pylab calls simply make
the appropriate forwarding calls. Is there a reason all of this
cannot be done in the relevant Axes methods, with the pylab call
simply forwarding on the *args and **kwargs?

Thanks,
JDH

···

On 2/27/07, Mark Bakker <markbak@...287...> wrote:

Thanks for the explanation, John.
I printed out the CODING_GUIDE (sorry, didn't know it existed).
The new function with the extra copy command is shown below.
Can we add this to pylab?

John -

Maybe I misunderstand you, but I thought the pylab interface
was invented to do very useful stuff (yet you want to prevent it
from doing something useful ??).

All the functionality is already in the API, but the calling sequence

is too lengthy and somewhat convoluted for interactive use.
The pylab interface is great for interactive use in my opinion.
The proposed aspect command falls right into that framework.
Eric suggested it a week or so ago, as he thought (and I agreed)
that the axis command in pylab was doing too many things already.

Mark

···

On 2/27/07, John Hunter <jdh2358@…287…> wrote:

On 2/27/07, Mark Bakker <markbak@…287…> wrote:

Thanks for the explanation, John.
I printed out the CODING_GUIDE (sorry, didn’t know it existed).
The new function with the extra copy command is shown below.

Can we add this to pylab?

Since Eric has been developing and maintaining the aspect stuff of
late, I’ll leave it to him to review and contribute. My one comment
is I want to make pylab as thin a wrapper as possible and where

possible prevent it from doing anything useful. That is, I’d like to
see all the functionality in the API, and the pylab calls simply make
the appropriate forwarding calls. Is there a reason all of this

cannot be done in the relevant Axes methods, with the pylab call
simply forwarding on the *args and **kwargs?

Thanks,
JDH

Mark Bakker wrote:

John -

Maybe I misunderstand you, but I thought the pylab interface
was invented to do very useful stuff (yet you want to prevent it
from doing something useful ??).

No, the point is to minimize differences between pylab functions and object methods, to make it as easy as possible for a person to do something interactive in pylab and then translate it into a more OO-style script. The present "axis" function is an example--it is a wrapper around Axes.axis(). Evolution from a Matlab legacy has made "axis" rather clunky, so the idea of an "aspect" method and function is reasonable.

All the functionality is already in the API, but the calling sequence
is too lengthy and somewhat convoluted for interactive use.
The pylab interface is great for interactive use in my opinion.
The proposed aspect command falls right into that framework.
Eric suggested it a week or so ago, as he thought (and I agreed)
that the axis command in pylab was doing too many things already.

Your implementation of aspect() does only a little more than Axes.set_aspect(), which is itself something of an anomaly among the "set_* commands (my fault). A little reorganizing may be in order; I'll look at that also.

Sorry to be sluggish on this.

Mark

     > Thanks for the explanation, John.
     > I printed out the CODING_GUIDE (sorry, didn't know it existed).

It is very new.

     > The new function with the extra copy command is shown below.
     > Can we add this to pylab?

    Since Eric has been developing and maintaining the aspect stuff of
    late, I'll leave it to him to review and contribute. My one comment
    is I want to make pylab as thin a wrapper as possible and where
    possible prevent it from doing anything useful. That is, I'd like to
    see all the functionality in the API, and the pylab calls simply make
    the appropriate forwarding calls. Is there a reason all of this
    cannot be done in the relevant Axes methods, with the pylab call
    simply forwarding on the *args and **kwargs?

This is what I had in mind.

···

On 2/27/07, *John Hunter* <jdh2358@...287... <mailto:jdh2358@…287…>> > wrote:
    On 2/27/07, Mark Bakker <markbak@...287... > <mailto:markbak@…287…>> wrote:

    Thanks,
    JDH

Eric Firing wrote:

make it as easy as possible for a person to do something interactive in pylab and then translate it into a more OO-style script.

Or even more - not use pylab at all. There is nothing inherent in OO
design that makes it necessary to write a bunch more code. It would be
nice if the OO interface were just as easy to use.

-Chris

···

--
Christopher Barker, Ph.D.
Oceanographer

Emergency Response Division
NOAA/NOS/OR&R (206) 526-6959 voice
7600 Sand Point Way NE (206) 526-6329 fax
Seattle, WA 98115 (206) 526-6317 main reception

Chris.Barker@...259...

Or even more - not use pylab at all. There is nothing inherent in OO
design that makes it necessary to write a bunch more code. It would be
nice if the OO interface were just as easy to use.

I don't agree with this at all. Inherent in OO design is object
creation, attribute setting and method calling. pylab automates some
of these steps, which in any OO design can be a little repetitive, via
figure management and current axes handling. Consider this current
use case of pylab

  > ipython -pylab
  >>> plot([1,2,3])

A figure window pops up and a plot appears inside an axes. My belief
is that try as you might, you won't create an object oriented API
which mimics this ease of use. Let's say you are willing to grant
pylab (or whatever you call your favorite API) control of managing of
the current figure via use of a 'figure' call. Then you can simplify
this to

  >>> fig = figure()
  >>> ax = fig.add_subplot(111)
  >>> ax.plot([1,2,3])

OK, that's not too bad and is how I work on a daily basis, but it is
still considerably more typing. So let's grant automatic current
Figure creation to the API. We'll retain control of creating the
Axes, but if there is not a Figure window to put it into, we'll
automatically create a Figure to hold it. Then we have something like

  >>> ax = subplot(111) # automatically create a Figure to hold the Axes
  >>> ax.plot([1,2,3])

That's only a little more work (and is supported with 'from pylab
import subplot'), but why do we have to manually instantiate the Axes
with 'subplot', let's autocreate it if it isn't there. Ok, let's make
a function 'plot' that will plot into the current Axes if it exists
and create one if it isn't there (and by implication create a Figure
if it isn't there)

  >>> plot([1,2,3])

OK, that's nice and easy, but we just reinvented pylab 'plot', which
really does nothing except manage the current Axes and Figure and
forward the rest on to Axes.plot. pylab does nothing except manage
the boiler plate that comes from using an OO framework -- basically it
automates object instantiation and method calling by inferring which
objects you want to create and which objects you want to forward the
method calls to. And in my opinion, it does so pretty well.

In an OO framework, this process of inference and forwarding is
prevented by design, and hence you have more work to do on the user
end, usually for good reason. I believe there is a tradeoff between
having explicit control -- managing axes and figures and explicitly
designating which objects get the attribute assignments and method
calls -- and convenience. pylab trades explicit control, which is not
to say it trades control, for less boilerplate code and more
convenience. pylab is a state machine -- it manages the current
figure and axes and forwards calls as appropriate to the active object
-- and state machines have proven very useful in other programming
paradigms, For example, the postscript and opengl languages are both
state machines, not object oriented languages, and both are very
useful for creating graphics,

The point of this exercise is that you can make the API progressively
easier to use by providing helper functions, but in the end, you'll
rewrite pylab or something like it. In my experience, save for a
little boilerplate for figure and axes creation, and the occasional
verbosity of some getters and setters, the current OO API is pretty
easy to use.

The one thing that is clearly more verbose than it needs to be to me
is the use of the setters and getters. One wants to do

  o = ax.text(1, 2, 'hi Chris')
  o.color = 'black'

rather than

  o = ax.text(1, 2, 'hi Chris')
  o.set_color('black')

and we could implement that fairly easy, saving five keystrokes.
Probably the reason there is limited impetus to do so is that it is
even easier (and less typing) to use keyword args in the current API
(and in pylab since pylab just forwards them to the API)

  ax.text(1, 2, 'hi mom', color='black')

and in fact using multiple kwargs is a good bit less typing than using
multiple properties. Eg, the following which you can do currently

  ax.text(1, 2, 'hi mom', color='black', size=14)

is a good bit easier than

  o = ax.text(1, 2, 'hi Chris')
  o.color = 'black'
  o.size = 14

and so on.

You've made the point about the API being hard to use many times, and
at least once you've made specific suggestions (eg adding
Figure.savefig) which we've implemented and I which find quite
useful. I suggest you start a wiki page on the matplotlib Cookbook
site listing the problems you see with the API and specific things you
would like to see changed, so the discussion can be more productive.

JDH

···

On 2/27/07, Christopher Barker <Chris.Barker@...259...> wrote:

John Hunter wrote:

There is nothing inherent in OO
design that makes it necessary to write a bunch more code.

I don't agree with this at all. Inherent in OO design is object
creation, attribute setting and method calling. pylab automates some
of these steps, which in any OO design can be a little repetitive, via
figure management and current axes handling.

OK, maybe there is inherently a little more code in an OO framework, but the case at hand was perhaps 4 lines of code wrapped up in a pylab function -- that could just as easily be 4 lines of code in a matplotlib class method.

Also, I think there is a distinction between talking about "functional vs. OO" and the automated figure and axis management.

For example, the standard way to manipulate objects with a functional interface is something like:

Afunction(AnObject, TheParameters)

in OO, it's:

AnObject.TheMethod(TheParamerters)

Exactly the same amount of typing, but I think the later is cleaner.

As far as pylab goes, the introduction of state: current axis and current figure, lets you avoid specifying the object you want to act on, so yes, you do save some typing there.

However, I do think that it would be possible to make a nice OO interface for interactive use -- perhaps that would mean keeping a current figure and axis, and I think with auto generation, we could get close:

>>> plot([1,2,3])

OK, that's nice and easy, but we just reinvented pylab 'plot', which
really does nothing except manage the current Axes and Figure and
forward the rest on to Axes.plot.

It is nice to have a really simple plot command. What would it do if we were trying to be fully OO? My key question is whether it would return and axis, a figure or both:

Fig, ax = plot([1,2,3])

then:

ax.xlabel("whatever")

isn't bad for me.

pylab does nothing except manage
the boiler plate that comes from using an OO framework -- basically it
automates object instantiation and method calling by inferring which
objects you want to create and which objects you want to forward the
method calls to. And in my opinion, it does so pretty well.

Yes, it does, and it does have key advantages for interactive use. I think where my opinions come from is two key points:

1) I never really do all that much interactively -- I used Matlab for years, and now Python for years. In both cases, I write maybe a few lines to test things interactively, but if I'm writing more than 3 lines or so, I write a small script -- one that I'm likely to want to paste into a larger code base at some point.

2) the pylab interface really does get in the way and make me less productive for larger bodies of code.

So I want ONE interface, and I want it optimized for large code bases, but still simple enough to not be painful for small scripts and interactive use.

In my experience, save for a
little boilerplate for figure and axes creation, and the occasional
verbosity of some getters and setters, the current OO API is pretty
easy to use.

I agree. I think we are close. This thread started because it was suggested that some code put into pylab be moved into the axis class (I think it was the axis class) so that OO users could have easy access to that functionality too.

The one thing that is clearly more verbose than it needs to be to me
is the use of the setters and getters.

Agreed.

Probably the reason there is limited impetus to do so is that it is
even easier (and less typing) to use keyword args in the current API
(and in pylab since pylab just forwards them to the API)

ax.text(1, 2, 'hi mom', color='black')

and in fact using multiple kwargs is a good bit less typing than using
multiple properties.

True, but what about:

ax.xlabel="a label"

and the like. I like properties -- they feel Pythonic to me.

I suggest you start a wiki page on the matplotlib Cookbook
site listing the problems you see with the API and specific things you
would like to see changed, so the discussion can be more productive.

Good idea. I've always intended to contribute more. I've mostly been waiting for a project where I'm really using MPL enough to know what I need. I've got one coming up, we'll see.

Meanwhile, I use a far too much time kibitzing on mailing lists...

-Chris

···

On 2/27/07, Christopher Barker <Chris.Barker@...259...> wrote:

--
Christopher Barker, Ph.D.
Oceanographer

Emergency Response Division
NOAA/NOS/OR&R (206) 526-6959 voice
7600 Sand Point Way NE (206) 526-6329 fax
Seattle, WA 98115 (206) 526-6317 main reception

Chris.Barker@...259...

Christopher Barker wrote:
[...]

It is nice to have a really simple plot command. What would it do if we were trying to be fully OO? My key question is whether it would return and axis, a figure or both:

Fig, ax = plot([1,2,3])

then:

ax.xlabel("whatever")

isn't bad for me.

Sometimes plot creates a figure, sometimes it creates an axes, sometimes neither, but it always creates one or more Line2D objects, so that is what it returns--a list of lines. As far as I can see, it *has* to return this, or something containing this, so that one can work later with these most fundamental objects that it makes. An alternative would be some sort of LineSet object like the ContourSet object returned by contour, with lots of extra information, but I don't know that there would be any advantage.

Anyway, the point is that your alternatives for plot to return would not work well in practice, but what it does return now works fine, both for plot() and for ax.plot().

Eric