5

The title pretty much says it. However, the way matplotlib is set up, it's not possible to simply inherit from Axes and have it work. The Axes object is never used directly, typically it's only returned from calls to subplot or other functions.

There's a couple reasons I want to do this. First, to reduce reproducing plots with similar parameters over and over. Something like this:

class LogTemp(plt.Axes):
    """ Axes to display temperature over time, in logscale """
    def __init__(self, *args, **kwargs):
        super.__init__(*args, **kwargs)
        self.set_xlabel("Time (s)")
        self.set_ylabel("Temperature(C)")
        self.set_yscale('log')

It wouldn't be hard to write a custom function for this, although it wouldn't be as flexible. The bigger reason is that I want to override some of the default behavior. As a very simple example, consider

class Negative(plt.Axes):
     """ Plots negative of arguments """
     def plot(self, x, y, *args, **kwargs):
         super().plot(x, -y, *args, **kwargs)

or

class Outliers(plt.Axes):
     """ Highlight outliers in plotted data """
     def plot(self, x, y, **kwargs):
         out = y > 3*y.std()
         super().plot(x, -y, **kwargs)
         super().plot(x[out], y[out], marker='x', linestyle='', **kwargs)       

Trying to modify more than one aspect of behavior will very quickly become messy if using functions.

However, I haven't found a way to have matplotlib easily handle new Axes classes. The docs don't mention it anywhere that I've seen. This question addresses inheriting from the Figure class. The custom class can then be passed into some matplotlib functions. An unanswered question here suggests the Axes aren't nearly as straightforward.

Update: It's possible to monkey patch matplotlib.axes.Axes to override the default behavior, but this can only be done once when the program is first executed. Using multiple custom Axes is not possible with this approach.

user2699
  • 2,927
  • 14
  • 31
  • 1
    The matplotlib documentation has an example of how to create a custom axes: [custom_projection_example](https://matplotlib.org/examples/api/custom_projection_example.html) There is also a complete [guide on creating scales and transformations](https://matplotlib.org/devel/add_new_projection.html). A usecase can e.g. be found [here](https://stackoverflow.com/questions/16705452/matplotlib-forcing-pan-zoom-to-constrain-to-x-axes). – ImportanceOfBeingErnest Feb 03 '18 at 10:29
  • At the end, I think that this is pretty much the same as [this question](https://stackoverflow.com/questions/44090563/in-python-how-can-i-inherit-and-override-a-method-on-a-class-instance-assignin) – ImportanceOfBeingErnest Feb 03 '18 at 10:29
  • @ImportanceOfBeingErnest, It's quite similar. That question is how to modify an already instantiated instance, although your answer covers the situation here. – user2699 Feb 03 '18 at 13:52

1 Answers1

7

I've found a good approach explained on github. Their goal was an Axes object without ticks or markers, which speeds up creation time significantly. It's possible to register "projections" with matplotlib, then use those.

For the example I've given, it can be done by

class Outliers(plt.Axes):
    """ Highlight outliers in plotted data """
    name = 'outliers'
    def plot(self, x, y, **kwargs):
         out = abs(y - y.mean()) > 3*y.std()
         super().plot(x, y, **kwargs)
         super().plot(x[out], y[out], marker='x', linestyle='', **kwargs)

import matplotlib.projections as proj
proj.register_projection(Outliers)

And it can then be used by

ax = f.add_subplot(1, 1, 1, projection='outliers')

or

fig, axes = plt.subplots(20,20, subplot_kw=dict(projection='outliers'))

The only changes required are a name variable in the class definition, then passing that name in the projection argument of subplot.

user2699
  • 2,927
  • 14
  • 31
  • 1
    Yeah this seems to be the 'offical' way to do this. I started down the road of using internal functions to create new axes and add them to the figure but its way more hassle than it's worth. I think matlotlib are sort of wrong to call this feature "projection", implying it is only useful or used for defining custom projections, when in reality it can be used for customising literally anything to do with axes. – Alex Jul 04 '22 at 12:30