Adding custom attributes to matplotlib Figure and Axes instances: ill-advised?

Question:

I noticed that I can add my own attributes to matplotlib.axes.Axes() and matplotlib.figure.Figure() instances. For instance,

import matplotlib as mpl
fig = mpl.figure.Figure()
ax = fig.add_subplot()
ax.foo = 'bar'

In practical terms, I might like to add a basemap instance to an axes object using something like

import mpl_toolkits.basemap as basemap    
ax.basemap = basemap.Basemap('mollweide', ax=ax)

So that I can add geographical features in a more object-oriented, intuitive way. Is this a documented/reliable feature of these objects, or is it accidental? In other words, can I "safely" use this?

Asked By: Luke Davis

||

Answers:

According to this example you can add any new property for custom Figure class. However for Axes class such task is more complicated. You have to make own Axes class

from matplotlib.pyplot import figure, show
from matplotlib.figure import Figure
from matplotlib.axes import Subplot
from mpl_toolkits.basemap import Basemap   

# custom Figure class with foo property  
class MyFigure(Figure):
    def __init__(self, *args, **kwargs):
        self.foo = kwargs.pop('foo', 'bar')
        Figure.__init__(self, *args, **kwargs)

# custom Axes class  
class MyAxes(Subplot):
    def __init__(self, *args, **kwargs):
        super(MyAxes, self).__init__(*args, **kwargs)

    # add my axes 
    @staticmethod
    def from_ax(ax=None, fig=None, *args, **kwargs):
        if ax is None:
            if fig is None: fig = figure(facecolor='w', edgecolor='w')
            ax = MyAxes(fig, 1, 1, 1, *args, **kwargs)
            fig.add_axes(ax)
        return ax

# test run
# custom figure
fig = figure(FigureClass=MyFigure, foo='foo')
print (type(fig), fig.foo)

# add to figure custom axes
ax = MyAxes.from_ax(fig=fig)
print (type(fig.gca()))

# create Basemap instance and store it to 'm' property
ax.m = Basemap(llcrnrlon=-119, llcrnrlat=22, urcrnrlon=-64, urcrnrlat=49, 
 projection='lcc', lat_1=33, lat_2=45, lon_0=-95, resolution='c')
ax.m.drawcoastlines(linewidth=.25)
ax.m.drawcountries(linewidth=.25)
ax.m.fillcontinents(color='coral', lake_color='aqua')

print (ax.m)
show()

Output:

<class '__main__.MyFigure'> foo
<class '__main__.MyAxes'>
<mpl_toolkits.basemap.Basemap object at 0x107fc0358>

enter image description here

However my opinion is that you do not need to make m property of axes class. You have to call all functions according to basemap in custom axes class i.e. in __init___ (or make special method like set_basemap).

Answered By: Serenity