Search code examples
pythonpython-3.xmatplotlibmatplotlib-basemap

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


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?


Solution

  • 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).