Skip to content

Cartopy axes_grid_basic example broken by Matplotlib 3.6 #24053

Closed
@dopplershift

Description

@dopplershift

Cartopy's axes_grid_basic was broken by Matplotlib 3.6. The relevant call is:

    axgr = AxesGrid(fig, 111, axes_class=axes_class,
                    nrows_ncols=(3, 2),
                    axes_pad=0.6,
                    cbar_location='right',
                    cbar_mode='single',
                    cbar_pad=0.2,
                    cbar_size='3%',
                    label_mode='')  # note the empty label_mode

In #23550, we began explicitly checking for the documented values of label_mode, ('All', 'L', '1'), which this runs afoul of. It appears the previous code let '' (or any option not handled) essentially work as a noop for set_label_mode().

Based on the output when the example worked, '1' would be fine behavior-wise, but that currently produces in this example:

/Users/rmay/repos/cartopy/examples/miscellanea/axes_grid_basic.py:48: MatplotlibDeprecationWarning: The resize_event function was deprecated in Matplotlib 3.6 and will be removed two minor releases later. Use callbacks.process('resize_event', ResizeEvent(...)) instead.
  fig = plt.figure()
Traceback (most recent call last):
  File "/Users/rmay/repos/cartopy/examples/miscellanea/axes_grid_basic.py", line 78, in <module>
    main()
  File "/Users/rmay/repos/cartopy/examples/miscellanea/axes_grid_basic.py", line 49, in main
    axgr = AxesGrid(fig, 111,
  File "/Users/rmay/miniconda3/envs/py310/lib/python3.10/site-packages/mpl_toolkits/axes_grid1/axes_grid.py", line 391, in __init__
    super().__init__(
  File "/Users/rmay/miniconda3/envs/py310/lib/python3.10/site-packages/mpl_toolkits/axes_grid1/axes_grid.py", line 174, in __init__
    self.set_label_mode(label_mode)
  File "/Users/rmay/miniconda3/envs/py310/lib/python3.10/site-packages/mpl_toolkits/axes_grid1/axes_grid.py", line 295, in set_label_mode
    _tick_only(ax, bottom_on=True, left_on=True)
  File "/Users/rmay/miniconda3/envs/py310/lib/python3.10/site-packages/mpl_toolkits/axes_grid1/axes_grid.py", line 16, in _tick_only
    ax.axis["bottom"].toggle(ticklabels=bottom_off, label=bottom_off)
TypeError: 'method' object is not subscriptable

This is because, while AxesGrid supposedly supports arbitrary classes by passing axes_class, there's a huge caveat: the default is mpl_toolkits.axes_grid1.mpl_axes.Axes (TERRIBLY CONFUSING NAME--it's imported as just Axes in axis_grid.py). This is a custom subclass that adds:

    @property
    def axis(self):
        return self._axislines

    def clear(self):
        # docstring inherited
        super().clear()
        # Init axis artists.
        self._axislines = self.AxisDict(self)
        self._axislines.update(
            bottom=SimpleAxisArtist(self.xaxis, 1, self.spines["bottom"]),
            top=SimpleAxisArtist(self.xaxis, 2, self.spines["top"]),
            left=SimpleAxisArtist(self.yaxis, 1, self.spines["left"]),
            right=SimpleAxisArtist(self.yaxis, 2, self.spines["right"]))

Options I can think of:

  1. Restore a noop option for set_label_mode
  2. Fix AxesGrid to work more generally with axes_class--monkey-patch/subclass to add the necessary modifications
  3. Document that axes_class needs to have certain behavior
  4. Fix AxesGrid to not rely on a property that OVERRIDES matplotlib.axes.Axes.axis with a different type that also manually dispatches to the unbound axis() method 😱
    class AxisDict(dict):
        def __init__(self, axes):
            self.axes = axes
            super().__init__()

        def __getitem__(self, k):
            if isinstance(k, tuple):
                r = SimpleChainedObjects(
                    # super() within a list comprehension needs explicit args.
                    [super(Axes.AxisDict, self).__getitem__(k1) for k1 in k])
                return r
            elif isinstance(k, slice):
                if k.start is None and k.stop is None and k.step is None:
                    return SimpleChainedObjects(list(self.values()))
                else:
                    raise ValueError("Unsupported slice")
            else:
                return dict.__getitem__(self, k)

        def __call__(self, *v, **kwargs):
            return maxes.Axes.axis(self.axes, *v, **kwargs)

Anyone have any opinions?

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions