123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257 |
- from matplotlib import _api, cbook
- import matplotlib.artist as martist
- import matplotlib.transforms as mtransforms
- from matplotlib.transforms import Bbox
- from .mpl_axes import Axes
- class ParasiteAxesBase:
- def __init__(self, parent_axes, aux_transform=None,
- *, viewlim_mode=None, **kwargs):
- self._parent_axes = parent_axes
- self.transAux = aux_transform
- self.set_viewlim_mode(viewlim_mode)
- kwargs["frameon"] = False
- super().__init__(parent_axes.figure, parent_axes._position, **kwargs)
- def clear(self):
- super().clear()
- martist.setp(self.get_children(), visible=False)
- self._get_lines = self._parent_axes._get_lines
- self._parent_axes.callbacks._connect_picklable(
- "xlim_changed", self._sync_lims)
- self._parent_axes.callbacks._connect_picklable(
- "ylim_changed", self._sync_lims)
- def pick(self, mouseevent):
- # This most likely goes to Artist.pick (depending on axes_class given
- # to the factory), which only handles pick events registered on the
- # axes associated with each child:
- super().pick(mouseevent)
- # But parasite axes are additionally given pick events from their host
- # axes (cf. HostAxesBase.pick), which we handle here:
- for a in self.get_children():
- if (hasattr(mouseevent.inaxes, "parasites")
- and self in mouseevent.inaxes.parasites):
- a.pick(mouseevent)
- # aux_transform support
- def _set_lim_and_transforms(self):
- if self.transAux is not None:
- self.transAxes = self._parent_axes.transAxes
- self.transData = self.transAux + self._parent_axes.transData
- self._xaxis_transform = mtransforms.blended_transform_factory(
- self.transData, self.transAxes)
- self._yaxis_transform = mtransforms.blended_transform_factory(
- self.transAxes, self.transData)
- else:
- super()._set_lim_and_transforms()
- def set_viewlim_mode(self, mode):
- _api.check_in_list([None, "equal", "transform"], mode=mode)
- self._viewlim_mode = mode
- def get_viewlim_mode(self):
- return self._viewlim_mode
- def _sync_lims(self, parent):
- viewlim = parent.viewLim.frozen()
- mode = self.get_viewlim_mode()
- if mode is None:
- pass
- elif mode == "equal":
- self.viewLim.set(viewlim)
- elif mode == "transform":
- self.viewLim.set(viewlim.transformed(self.transAux.inverted()))
- else:
- _api.check_in_list([None, "equal", "transform"], mode=mode)
- # end of aux_transform support
- parasite_axes_class_factory = cbook._make_class_factory(
- ParasiteAxesBase, "{}Parasite")
- ParasiteAxes = parasite_axes_class_factory(Axes)
- class HostAxesBase:
- def __init__(self, *args, **kwargs):
- self.parasites = []
- super().__init__(*args, **kwargs)
- def get_aux_axes(
- self, tr=None, viewlim_mode="equal", axes_class=None, **kwargs):
- """
- Add a parasite axes to this host.
- Despite this method's name, this should actually be thought of as an
- ``add_parasite_axes`` method.
- .. versionchanged:: 3.7
- Defaults to same base axes class as host axes.
- Parameters
- ----------
- tr : `~matplotlib.transforms.Transform` or None, default: None
- If a `.Transform`, the following relation will hold:
- ``parasite.transData = tr + host.transData``.
- If None, the parasite's and the host's ``transData`` are unrelated.
- viewlim_mode : {"equal", "transform", None}, default: "equal"
- How the parasite's view limits are set: directly equal to the
- parent axes ("equal"), equal after application of *tr*
- ("transform"), or independently (None).
- axes_class : subclass type of `~matplotlib.axes.Axes`, optional
- The `~.axes.Axes` subclass that is instantiated. If None, the base
- class of the host axes is used.
- **kwargs
- Other parameters are forwarded to the parasite axes constructor.
- """
- if axes_class is None:
- axes_class = self._base_axes_class
- parasite_axes_class = parasite_axes_class_factory(axes_class)
- ax2 = parasite_axes_class(
- self, tr, viewlim_mode=viewlim_mode, **kwargs)
- # note that ax2.transData == tr + ax1.transData
- # Anything you draw in ax2 will match the ticks and grids of ax1.
- self.parasites.append(ax2)
- ax2._remove_method = self.parasites.remove
- return ax2
- def draw(self, renderer):
- orig_children_len = len(self._children)
- locator = self.get_axes_locator()
- if locator:
- pos = locator(self, renderer)
- self.set_position(pos, which="active")
- self.apply_aspect(pos)
- else:
- self.apply_aspect()
- rect = self.get_position()
- for ax in self.parasites:
- ax.apply_aspect(rect)
- self._children.extend(ax.get_children())
- super().draw(renderer)
- del self._children[orig_children_len:]
- def clear(self):
- super().clear()
- for ax in self.parasites:
- ax.clear()
- def pick(self, mouseevent):
- super().pick(mouseevent)
- # Also pass pick events on to parasite axes and, in turn, their
- # children (cf. ParasiteAxesBase.pick)
- for a in self.parasites:
- a.pick(mouseevent)
- def twinx(self, axes_class=None):
- """
- Create a twin of Axes with a shared x-axis but independent y-axis.
- The y-axis of self will have ticks on the left and the returned axes
- will have ticks on the right.
- """
- ax = self._add_twin_axes(axes_class, sharex=self)
- self.axis["right"].set_visible(False)
- ax.axis["right"].set_visible(True)
- ax.axis["left", "top", "bottom"].set_visible(False)
- return ax
- def twiny(self, axes_class=None):
- """
- Create a twin of Axes with a shared y-axis but independent x-axis.
- The x-axis of self will have ticks on the bottom and the returned axes
- will have ticks on the top.
- """
- ax = self._add_twin_axes(axes_class, sharey=self)
- self.axis["top"].set_visible(False)
- ax.axis["top"].set_visible(True)
- ax.axis["left", "right", "bottom"].set_visible(False)
- return ax
- def twin(self, aux_trans=None, axes_class=None):
- """
- Create a twin of Axes with no shared axis.
- While self will have ticks on the left and bottom axis, the returned
- axes will have ticks on the top and right axis.
- """
- if aux_trans is None:
- aux_trans = mtransforms.IdentityTransform()
- ax = self._add_twin_axes(
- axes_class, aux_transform=aux_trans, viewlim_mode="transform")
- self.axis["top", "right"].set_visible(False)
- ax.axis["top", "right"].set_visible(True)
- ax.axis["left", "bottom"].set_visible(False)
- return ax
- def _add_twin_axes(self, axes_class, **kwargs):
- """
- Helper for `.twinx`/`.twiny`/`.twin`.
- *kwargs* are forwarded to the parasite axes constructor.
- """
- if axes_class is None:
- axes_class = self._base_axes_class
- ax = parasite_axes_class_factory(axes_class)(self, **kwargs)
- self.parasites.append(ax)
- ax._remove_method = self._remove_any_twin
- return ax
- def _remove_any_twin(self, ax):
- self.parasites.remove(ax)
- restore = ["top", "right"]
- if ax._sharex:
- restore.remove("top")
- if ax._sharey:
- restore.remove("right")
- self.axis[tuple(restore)].set_visible(True)
- self.axis[tuple(restore)].toggle(ticklabels=False, label=False)
- @_api.make_keyword_only("3.8", "call_axes_locator")
- def get_tightbbox(self, renderer=None, call_axes_locator=True,
- bbox_extra_artists=None):
- bbs = [
- *[ax.get_tightbbox(renderer, call_axes_locator=call_axes_locator)
- for ax in self.parasites],
- super().get_tightbbox(renderer,
- call_axes_locator=call_axes_locator,
- bbox_extra_artists=bbox_extra_artists)]
- return Bbox.union([b for b in bbs if b.width != 0 or b.height != 0])
- host_axes_class_factory = host_subplot_class_factory = \
- cbook._make_class_factory(HostAxesBase, "{}HostAxes", "_base_axes_class")
- HostAxes = SubplotHost = host_axes_class_factory(Axes)
- def host_axes(*args, axes_class=Axes, figure=None, **kwargs):
- """
- Create axes that can act as a hosts to parasitic axes.
- Parameters
- ----------
- figure : `~matplotlib.figure.Figure`
- Figure to which the axes will be added. Defaults to the current figure
- `.pyplot.gcf()`.
- *args, **kwargs
- Will be passed on to the underlying `~.axes.Axes` object creation.
- """
- import matplotlib.pyplot as plt
- host_axes_class = host_axes_class_factory(axes_class)
- if figure is None:
- figure = plt.gcf()
- ax = host_axes_class(figure, *args, **kwargs)
- figure.add_axes(ax)
- return ax
- host_subplot = host_axes
|