6

I am building a class of plot tools for a specific experiment. I currently have two plot methods, a static plot using imshow(), and a "movie" format also using imshow() .

Both methods and any future methods, get parameters that are the same for any specific plotting method that I might write. I have all those parameters in a config object at the time the plot class is used.

I don't want to rewrite code in every plot method. I would like to initialize an object (AxesImage I think) that will have these args set: vmin, vmax, extent_dim, Xlocs, Xlabels, Ylocs, Ylabels.

Then I just pass that object to various methods that do some other specific thing. I don't understand how to do this...

import matplotlib.pyplot as plt

data = data_dict[type] # could be real part of a complex number, phase, or the mag...
v_min, v_max = self.get_data_type_scale(data_dict, Type)
freq = data_dict['freq']

# essentially sets the aspect of the plot since the x and y resolutions could be different   
extent_dim = self._get_extent(2)
# gets the labels for physical dimensions of the experiment
Xlocs,Xlabels,Ylocs,Ylabels = self._get_ticks(5,5,extent_dim)

# in the guts of a plot method, the basic idea is the call below.  

plt.imshow(data[0,:,:],cmap='jet',vmin=v_min,...
vmax=v_max,origin='lower', extent = extent_dim)

plt.title('Type:  %s  Freq: %.3e Hz' %(Type,data_dict['freq'][0]) )
plt.xticks(Xlocs, Xlabels)
plt.yticks(Ylocs,Ylabels)
wbg
  • 866
  • 3
  • 14
  • 34
  • What is your question exactly? If you want to initialize a `figure` object, then just do it. `fig = figure()` is all you need to do. – Phillip Cloud Aug 17 '13 at 18:17
  • Let's say I make a handle to my plot: im = plt.imshow(), then I can use im.set_extent((0,32,0,32)). Is this the proper way to pass around a plot object to methods. HOw do i get the plot to show? im.show() doesn't appear to work. Basically, how to I write good OO code with plots, where several arguments can be set in the __init__() and a reference to the object just passed to various methods. – wbg Aug 18 '13 at 00:20
  • What do you mean a handle? That sounds like MATLAB lingo, which is confusing here since Python has one thing: objects. In `matplotlib` there are `Axes` objects, `Figure` objects and tons of others. `im = imshow(randn(10, 10))` returns an `AxesImage` object. So...which one **exactly** are you talking about. – Phillip Cloud Aug 18 '13 at 00:23
  • handle is bad...old matlab habit. I suspect I mean the AxesImage object. – wbg Aug 18 '13 at 00:26
  • Would you mind changing the title of your question to something a bit more descriptive, like 'changing default values of arguments to pyplot functions'. Play jeopardy on the answer ;) – tacaswell Aug 20 '13 at 05:22
  • @tcaswell, I'm struggling with the name of the question. I feel like, yes, I want to know how to modify a plot's properties but it's also about how to write OO code with matplotlib. I don't want to repeat code, e.g., every plot method has a call to change the axes to the same values. I am also trying to control and remove choice for something that should be decided when the experimental conditions are conceived. – wbg Aug 21 '13 at 05:29

2 Answers2

13

You need to understand a bit of architecture of matplotlib first (see here for a long article by the founder and current lead developer). At the bottom of the backend layer which deals with rendering and talking to the hardware. On top of that layer are artists which know how to draw them selves by tell the backend object what to do. On top of that layer is the pyplot state machine interface which mimics MATLAB.

Everything you see in a figure is represented internally as an Artist and artists can contain other artists. For example, the Axes object keeps track of it's children Artists which are the axes spines, tickes, labels, your lines or images etc and Axes objects are children of Figure objects. When you tell a figure to draw itself (via fig.canvas.draw()) all the children artists are drawn recursively.

One draw back of this design is that a given instantiation of an Artist can be in exactly one figure (and moving them between figures is hard) so you can't make a AxesImage object and then keep reusing it.

This design also separates what Artists know about. Axes objects know about things like tick location and labels and the display range (which it does by knowing about Axis object, but that is getting even more into the weeds). Things like vmin and vmax are encapsulated in Normalize (doc) objects which the AxesImage keeps track of. This means that you will need to separate how you deal with everything on your list.

I would suggest either using a factory-like pattern here, or a curry-like pattern

Factory-like:

def set_up_axes(some, arguements):
    '''
    Factory to make configured axes (
    '''
    fig, ax = plt.subplots(1, 1) # or what ever layout you want
    ax.set_*(...)
    return fig, ax


my_norm = matplotlib.colors.Normalize(vmin, mmax) # or write a factory to do fancier stuff
fig, ax = set_up_axes(...)
ax.imshow(..., norm=my_norm)
fig2, ax2 = set_up_axes(...)
ax2.imshow(..., norm=mynorm)

You can wrap up a whole set of kwargs to easily re-use them as such:

my_imshow_args = {'extent':[...],
                  'interpolation':'nearest',
                  'norm': my_norm,
                   ...}

ax2.imshow(..., **my_imshow_args)

Curry-like:

def my_imshow(im, ax=None, *args, **kwargs):
    if ax is None:
        ax = plt.gca()
    # do all of your axes set up
    ax.set_xlim(..)

    # set default vmin and vmax
    # you can drop some of these conditionals if you don't want to be
    # able to explicitly override the defaults
    if 'norm' not in kwargs:
        vmin = kwargs.pop('vmin', None)
        vmax = kwargs.pop('vmax', None)
        if vmin is None:
            vmin = default_vmin # or what ever
        if vmax is None:
            vmax = default_vmax
        my_norm = matplotlib.colors.Normalize(vmin, mmax)
        kwargs['norm'] = norm

    # add a similar block for `extent` 
    # or any other kwargs you want to change the default of

    ax.figure.canvas.draw() # if you want to force a re-draw
    return ax.imshow(im, *args, **kwargs)

If you want to be super clever, you can monkey-patch plt.imshow with your version

plt.imshow = my_imshow

There is also the rcParams interface which will allow you to change the default values of many bits and pieces of matplotlib in a global way.

And yet another way to accomplish this (through partial)

Community
  • 1
  • 1
tacaswell
  • 84,579
  • 22
  • 210
  • 199
  • That's very helpful... :) I'll be referring to this for a while. Thanks so much. – wbg Aug 20 '13 at 04:35
2

To show a plot you'll want to use fig.canvas.draw() where fig is an instance of the Figure class. fig.canvas.draw() is the API version of the interactive shell (read: pylab) function draw()

If you need to get the Axes or Figure from an AxesImage object you can call either im.get_axes() or im.get_figure(), respectively.

As far as writing "good" object-oriented code the user interface examples might be good place to start.

Phillip Cloud
  • 24,919
  • 11
  • 68
  • 88