_subplots.py 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  1. import functools
  2. import uuid
  3. from matplotlib import cbook, docstring
  4. import matplotlib.artist as martist
  5. from matplotlib.axes._axes import Axes
  6. from matplotlib.gridspec import GridSpec, SubplotSpec
  7. import matplotlib._layoutbox as layoutbox
  8. class SubplotBase:
  9. """
  10. Base class for subplots, which are :class:`Axes` instances with
  11. additional methods to facilitate generating and manipulating a set
  12. of :class:`Axes` within a figure.
  13. """
  14. def __init__(self, fig, *args, **kwargs):
  15. """
  16. Parameters
  17. ----------
  18. fig : `matplotlib.figure.Figure`
  19. *args : tuple (*nrows*, *ncols*, *index*) or int
  20. The array of subplots in the figure has dimensions ``(nrows,
  21. ncols)``, and *index* is the index of the subplot being created.
  22. *index* starts at 1 in the upper left corner and increases to the
  23. right.
  24. If *nrows*, *ncols*, and *index* are all single digit numbers, then
  25. *args* can be passed as a single 3-digit number (e.g. 234 for
  26. (2, 3, 4)).
  27. **kwargs
  28. Keyword arguments are passed to the Axes (sub)class constructor.
  29. """
  30. self.figure = fig
  31. self._subplotspec = SubplotSpec._from_subplot_args(fig, args)
  32. self.update_params()
  33. # _axes_class is set in the subplot_class_factory
  34. self._axes_class.__init__(self, fig, self.figbox, **kwargs)
  35. # add a layout box to this, for both the full axis, and the poss
  36. # of the axis. We need both because the axes may become smaller
  37. # due to parasitic axes and hence no longer fill the subplotspec.
  38. if self._subplotspec._layoutbox is None:
  39. self._layoutbox = None
  40. self._poslayoutbox = None
  41. else:
  42. name = self._subplotspec._layoutbox.name + '.ax'
  43. name = name + layoutbox.seq_id()
  44. self._layoutbox = layoutbox.LayoutBox(
  45. parent=self._subplotspec._layoutbox,
  46. name=name,
  47. artist=self)
  48. self._poslayoutbox = layoutbox.LayoutBox(
  49. parent=self._layoutbox,
  50. name=self._layoutbox.name+'.pos',
  51. pos=True, subplot=True, artist=self)
  52. def __reduce__(self):
  53. # get the first axes class which does not inherit from a subplotbase
  54. axes_class = next(
  55. c for c in type(self).__mro__
  56. if issubclass(c, Axes) and not issubclass(c, SubplotBase))
  57. return (_picklable_subplot_class_constructor,
  58. (axes_class,),
  59. self.__getstate__())
  60. def get_geometry(self):
  61. """Get the subplot geometry, e.g., (2, 2, 3)."""
  62. rows, cols, num1, num2 = self.get_subplotspec().get_geometry()
  63. return rows, cols, num1 + 1 # for compatibility
  64. # COVERAGE NOTE: Never used internally or from examples
  65. def change_geometry(self, numrows, numcols, num):
  66. """Change subplot geometry, e.g., from (1, 1, 1) to (2, 2, 3)."""
  67. self._subplotspec = GridSpec(numrows, numcols,
  68. figure=self.figure)[num - 1]
  69. self.update_params()
  70. self.set_position(self.figbox)
  71. def get_subplotspec(self):
  72. """Return the `.SubplotSpec` instance associated with the subplot."""
  73. return self._subplotspec
  74. def set_subplotspec(self, subplotspec):
  75. """Set the `.SubplotSpec`. instance associated with the subplot."""
  76. self._subplotspec = subplotspec
  77. def get_gridspec(self):
  78. """Return the `.GridSpec` instance associated with the subplot."""
  79. return self._subplotspec.get_gridspec()
  80. def update_params(self):
  81. """Update the subplot position from ``self.figure.subplotpars``."""
  82. self.figbox, _, _, self.numRows, self.numCols = \
  83. self.get_subplotspec().get_position(self.figure,
  84. return_all=True)
  85. @cbook.deprecated("3.2", alternative="ax.get_subplotspec().rowspan.start")
  86. @property
  87. def rowNum(self):
  88. return self.get_subplotspec().rowspan.start
  89. @cbook.deprecated("3.2", alternative="ax.get_subplotspec().colspan.start")
  90. @property
  91. def colNum(self):
  92. return self.get_subplotspec().colspan.start
  93. def is_first_row(self):
  94. return self.get_subplotspec().rowspan.start == 0
  95. def is_last_row(self):
  96. return self.get_subplotspec().rowspan.stop == self.get_gridspec().nrows
  97. def is_first_col(self):
  98. return self.get_subplotspec().colspan.start == 0
  99. def is_last_col(self):
  100. return self.get_subplotspec().colspan.stop == self.get_gridspec().ncols
  101. def label_outer(self):
  102. """
  103. Only show "outer" labels and tick labels.
  104. x-labels are only kept for subplots on the last row; y-labels only for
  105. subplots on the first column.
  106. """
  107. lastrow = self.is_last_row()
  108. firstcol = self.is_first_col()
  109. if not lastrow:
  110. for label in self.get_xticklabels(which="both"):
  111. label.set_visible(False)
  112. self.get_xaxis().get_offset_text().set_visible(False)
  113. self.set_xlabel("")
  114. if not firstcol:
  115. for label in self.get_yticklabels(which="both"):
  116. label.set_visible(False)
  117. self.get_yaxis().get_offset_text().set_visible(False)
  118. self.set_ylabel("")
  119. def _make_twin_axes(self, *args, **kwargs):
  120. """Make a twinx axes of self. This is used for twinx and twiny."""
  121. if 'sharex' in kwargs and 'sharey' in kwargs:
  122. # The following line is added in v2.2 to avoid breaking Seaborn,
  123. # which currently uses this internal API.
  124. if kwargs["sharex"] is not self and kwargs["sharey"] is not self:
  125. raise ValueError("Twinned Axes may share only one axis")
  126. # The dance here with label is to force add_subplot() to create a new
  127. # Axes (by passing in a label never seen before). Note that this does
  128. # not affect plot reactivation by subplot() as twin axes can never be
  129. # reactivated by subplot().
  130. sentinel = str(uuid.uuid4())
  131. real_label = kwargs.pop("label", sentinel)
  132. twin = self.figure.add_subplot(
  133. self.get_subplotspec(), *args, label=sentinel, **kwargs)
  134. if real_label is not sentinel:
  135. twin.set_label(real_label)
  136. self.set_adjustable('datalim')
  137. twin.set_adjustable('datalim')
  138. if self._layoutbox is not None and twin._layoutbox is not None:
  139. # make the layout boxes be explicitly the same
  140. twin._layoutbox.constrain_same(self._layoutbox)
  141. twin._poslayoutbox.constrain_same(self._poslayoutbox)
  142. self._twinned_axes.join(self, twin)
  143. return twin
  144. def __repr__(self):
  145. fields = []
  146. if self.get_label():
  147. fields += [f"label={self.get_label()!r}"]
  148. titles = []
  149. for k in ["left", "center", "right"]:
  150. title = self.get_title(loc=k)
  151. if title:
  152. titles.append(f"{k!r}:{title!r}")
  153. if titles:
  154. fields += ["title={" + ",".join(titles) + "}"]
  155. if self.get_xlabel():
  156. fields += [f"xlabel={self.get_xlabel()!r}"]
  157. if self.get_ylabel():
  158. fields += [f"ylabel={self.get_ylabel()!r}"]
  159. return f"<{self.__class__.__name__}:" + ", ".join(fields) + ">"
  160. # this here to support cartopy which was using a private part of the
  161. # API to register their Axes subclasses.
  162. # In 3.1 this should be changed to a dict subclass that warns on use
  163. # In 3.3 to a dict subclass that raises a useful exception on use
  164. # In 3.4 should be removed
  165. # The slow timeline is to give cartopy enough time to get several
  166. # release out before we break them.
  167. _subplot_classes = {}
  168. @functools.lru_cache(None)
  169. def subplot_class_factory(axes_class=None):
  170. """
  171. Make a new class that inherits from `.SubplotBase` and the
  172. given axes_class (which is assumed to be a subclass of `.axes.Axes`).
  173. This is perhaps a little bit roundabout to make a new class on
  174. the fly like this, but it means that a new Subplot class does
  175. not have to be created for every type of Axes.
  176. """
  177. if axes_class is None:
  178. cbook.warn_deprecated(
  179. "3.3", message="Support for passing None to subplot_class_factory "
  180. "is deprecated since %(since)s; explicitly pass the default Axes "
  181. "class instead. This will become an error %(removal)s.")
  182. axes_class = Axes
  183. try:
  184. # Avoid creating two different instances of GeoAxesSubplot...
  185. # Only a temporary backcompat fix. This should be removed in
  186. # 3.4
  187. return next(cls for cls in SubplotBase.__subclasses__()
  188. if cls.__bases__ == (SubplotBase, axes_class))
  189. except StopIteration:
  190. return type("%sSubplot" % axes_class.__name__,
  191. (SubplotBase, axes_class),
  192. {'_axes_class': axes_class})
  193. Subplot = subplot_class_factory(Axes) # Provided for backward compatibility.
  194. def _picklable_subplot_class_constructor(axes_class):
  195. """
  196. Stub factory that returns an empty instance of the appropriate subplot
  197. class when called with an axes class. This is purely to allow pickling of
  198. Axes and Subplots.
  199. """
  200. subplot_class = subplot_class_factory(axes_class)
  201. return subplot_class.__new__(subplot_class)
  202. docstring.interpd.update(Axes=martist.kwdoc(Axes))
  203. docstring.dedent_interpd(Axes.__init__)
  204. docstring.interpd.update(Subplot=martist.kwdoc(Axes))