From e418f5ce8cd509585911aa9e45e0c54bb15f2a02 Mon Sep 17 00:00:00 2001 From: weijili Date: Tue, 5 Apr 2022 15:50:26 -0400 Subject: [PATCH] Use axes=self as an extra args in parasite_axes Right now parasite_axes just use self._parent_axes._get_lines as self._get_lines, but it can't update the axes unit when there are twin axes. Therefore, we need to provide axes=self as an extra args to handle this. We also need to change the callees to use axes in kwargs when provided. The test creates a plot with twin axes where both axes have units. It then checks whether units are appended correctly on the respective axes. The code base without the modification fails the unit test whereas the modification makes it pass the unit test. --- lib/matplotlib/axes/_axes.py | 6 ++-- lib/matplotlib/axes/_base.py | 36 +++++++++---------- .../axes_grid1/tests/test_axes_grid1.py | 10 +++++- lib/mpl_toolkits/mplot3d/axes3d.py | 2 +- 4 files changed, 31 insertions(+), 23 deletions(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 5369eadbdefd..348347e32c19 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -1720,7 +1720,7 @@ def plot(self, *args, scalex=True, scaley=True, data=None, **kwargs): (``'green'``) or hex strings (``'#008000'``). """ kwargs = cbook.normalize_kwargs(kwargs, mlines.Line2D) - lines = [*self._get_lines(*args, data=data, **kwargs)] + lines = [*self._get_lines(self, *args, data=data, **kwargs)] for line in lines: self.add_line(line) if scalex: @@ -3578,7 +3578,7 @@ def _upcast_err(err): # that would call self._process_unit_info again, and do other indirect # data processing. (data_line, base_style), = self._get_lines._plot_args( - (x, y) if fmt == '' else (x, y, fmt), kwargs, return_kwargs=True) + self, (x, y) if fmt == '' else (x, y, fmt), kwargs, return_kwargs=True) # Do this after creating `data_line` to avoid modifying `base_style`. if barsabove: @@ -5286,7 +5286,7 @@ def fill(self, *args, data=None, **kwargs): # For compatibility(!), get aliases from Line2D rather than Patch. kwargs = cbook.normalize_kwargs(kwargs, mlines.Line2D) # _get_patches_for_fill returns a generator, convert it to a list. - patches = [*self._get_patches_for_fill(*args, data=data, **kwargs)] + patches = [*self._get_patches_for_fill(self, *args, data=data, **kwargs)] for poly in patches: self.add_patch(poly) self._request_autoscale_view() diff --git a/lib/matplotlib/axes/_base.py b/lib/matplotlib/axes/_base.py index fb04d917720f..3ba07a190924 100644 --- a/lib/matplotlib/axes/_base.py +++ b/lib/matplotlib/axes/_base.py @@ -219,14 +219,14 @@ class _process_plot_var_args: an arbitrary number of *x*, *y*, *fmt* are allowed """ - def __init__(self, axes, command='plot'): - self.axes = axes + + def __init__(self, command='plot'): self.command = command self.set_prop_cycle(None) def __getstate__(self): # note: it is not possible to pickle a generator (and thus a cycler). - return {'axes': self.axes, 'command': self.command} + return {'command': self.command} def __setstate__(self, state): self.__dict__ = state.copy() @@ -238,8 +238,8 @@ def set_prop_cycle(self, cycler): self.prop_cycler = itertools.cycle(cycler) self._prop_keys = cycler.keys # This should make a copy - def __call__(self, *args, data=None, **kwargs): - self.axes._process_unit_info(kwargs=kwargs) + def __call__(self, axes, *args, data=None, **kwargs): + axes._process_unit_info(kwargs=kwargs) for pos_only in "xy": if pos_only in kwargs: @@ -309,7 +309,7 @@ def __call__(self, *args, data=None, **kwargs): this += args[0], args = args[1:] yield from self._plot_args( - this, kwargs, ambiguous_fmt_datakey=ambiguous_fmt_datakey) + axes, this, kwargs, ambiguous_fmt_datakey=ambiguous_fmt_datakey) def get_next_color(self): """Return the next color in the cycle.""" @@ -344,17 +344,17 @@ def _setdefaults(self, defaults, kw): if kw.get(k, None) is None: kw[k] = defaults[k] - def _makeline(self, x, y, kw, kwargs): + def _makeline(self, axes, x, y, kw, kwargs): kw = {**kw, **kwargs} # Don't modify the original kw. default_dict = self._getdefaults(set(), kw) self._setdefaults(default_dict, kw) seg = mlines.Line2D(x, y, **kw) return seg, kw - def _makefill(self, x, y, kw, kwargs): + def _makefill(self, axes, x, y, kw, kwargs): # Polygon doesn't directly support unitized inputs. - x = self.axes.convert_xunits(x) - y = self.axes.convert_yunits(y) + x = axes.convert_xunits(x) + y = axes.convert_yunits(y) kw = kw.copy() # Don't modify the original kw. kwargs = kwargs.copy() @@ -403,7 +403,7 @@ def _makefill(self, x, y, kw, kwargs): seg.set(**kwargs) return seg, kwargs - def _plot_args(self, tup, kwargs, *, + def _plot_args(self, axes, tup, kwargs, *, return_kwargs=False, ambiguous_fmt_datakey=False): """ Process the arguments of ``plot([x], y, [fmt], **kwargs)`` calls. @@ -495,10 +495,10 @@ def _plot_args(self, tup, kwargs, *, else: x, y = index_of(xy[-1]) - if self.axes.xaxis is not None: - self.axes.xaxis.update_units(x) - if self.axes.yaxis is not None: - self.axes.yaxis.update_units(y) + if axes.xaxis is not None: + axes.xaxis.update_units(x) + if axes.yaxis is not None: + axes.yaxis.update_units(y) if x.shape[0] != y.shape[0]: raise ValueError(f"x and y must have same first dimension, but " @@ -534,7 +534,7 @@ def _plot_args(self, tup, kwargs, *, else: labels = [label] * n_datasets - result = (make_artist(x[:, j % ncx], y[:, j % ncy], kw, + result = (make_artist(axes, x[:, j % ncx], y[:, j % ncy], kw, {**kwargs, 'label': label}) for j, label in enumerate(labels)) @@ -1292,8 +1292,8 @@ def __clear(self): self._tight = None self._use_sticky_edges = True - self._get_lines = _process_plot_var_args(self) - self._get_patches_for_fill = _process_plot_var_args(self, 'fill') + self._get_lines = _process_plot_var_args() + self._get_patches_for_fill = _process_plot_var_args('fill') self._gridOn = mpl.rcParams['axes.grid'] old_children, self._children = self._children, [] diff --git a/lib/mpl_toolkits/axes_grid1/tests/test_axes_grid1.py b/lib/mpl_toolkits/axes_grid1/tests/test_axes_grid1.py index b61574787772..60c99370c211 100644 --- a/lib/mpl_toolkits/axes_grid1/tests/test_axes_grid1.py +++ b/lib/mpl_toolkits/axes_grid1/tests/test_axes_grid1.py @@ -27,7 +27,6 @@ zoomed_inset_axes, mark_inset, inset_axes, BboxConnectorPatch, InsetPosition) import mpl_toolkits.axes_grid1.mpl_axes - import pytest import numpy as np @@ -91,6 +90,15 @@ def test_twin_axes_empty_and_removed(): plt.subplots_adjust(wspace=0.5, hspace=1) +def test_twin_axes_both_with_units(): + host = host_subplot(111) + host.plot_date([0, 1, 2], [0, 1, 2], xdate=False, ydate=True) + twin = host.twinx() + twin.plot(["a", "b", "c"]) + assert host.get_yticklabels()[0].get_text() == "00:00:00" + assert twin.get_yticklabels()[0].get_text() == "a" + + def test_axesgrid_colorbar_log_smoketest(): fig = plt.figure() grid = AxesGrid(fig, 111, # modified to be only subplot diff --git a/lib/mpl_toolkits/mplot3d/axes3d.py b/lib/mpl_toolkits/mplot3d/axes3d.py index cf2722eb4aae..44cb5812fdaa 100644 --- a/lib/mpl_toolkits/mplot3d/axes3d.py +++ b/lib/mpl_toolkits/mplot3d/axes3d.py @@ -3005,7 +3005,7 @@ def errorbar(self, x, y, z, zerr=None, yerr=None, xerr=None, fmt='', # that would call self._process_unit_info again, and do other indirect # data processing. (data_line, base_style), = self._get_lines._plot_args( - (x, y) if fmt == '' else (x, y, fmt), kwargs, return_kwargs=True) + self, (x, y) if fmt == '' else (x, y, fmt), kwargs, return_kwargs=True) art3d.line_2d_to_3d(data_line, zs=z) # Do this after creating `data_line` to avoid modifying `base_style`.