Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1import functools 

2import uuid 

3 

4from matplotlib import cbook, docstring 

5import matplotlib.artist as martist 

6from matplotlib.axes._axes import Axes 

7from matplotlib.gridspec import GridSpec, SubplotSpec 

8import matplotlib._layoutbox as layoutbox 

9 

10 

11class SubplotBase: 

12 """ 

13 Base class for subplots, which are :class:`Axes` instances with 

14 additional methods to facilitate generating and manipulating a set 

15 of :class:`Axes` within a figure. 

16 """ 

17 

18 def __init__(self, fig, *args, **kwargs): 

19 """ 

20 Parameters 

21 ---------- 

22 fig : `matplotlib.figure.Figure` 

23 

24 *args : tuple (*nrows*, *ncols*, *index*) or int 

25 The array of subplots in the figure has dimensions ``(nrows, 

26 ncols)``, and *index* is the index of the subplot being created. 

27 *index* starts at 1 in the upper left corner and increases to the 

28 right. 

29 

30 If *nrows*, *ncols*, and *index* are all single digit numbers, then 

31 *args* can be passed as a single 3-digit number (e.g. 234 for 

32 (2, 3, 4)). 

33 """ 

34 

35 self.figure = fig 

36 

37 if len(args) == 1: 

38 if isinstance(args[0], SubplotSpec): 

39 self._subplotspec = args[0] 

40 else: 

41 try: 

42 s = str(int(args[0])) 

43 rows, cols, num = map(int, s) 

44 except ValueError: 

45 raise ValueError('Single argument to subplot must be ' 

46 'a 3-digit integer') 

47 self._subplotspec = GridSpec(rows, cols, 

48 figure=self.figure)[num - 1] 

49 # num - 1 for converting from MATLAB to python indexing 

50 elif len(args) == 3: 

51 rows, cols, num = args 

52 rows = int(rows) 

53 cols = int(cols) 

54 if rows <= 0: 

55 raise ValueError(f'Number of rows must be > 0, not {rows}') 

56 if cols <= 0: 

57 raise ValueError(f'Number of columns must be > 0, not {cols}') 

58 if isinstance(num, tuple) and len(num) == 2: 

59 num = [int(n) for n in num] 

60 self._subplotspec = GridSpec( 

61 rows, cols, 

62 figure=self.figure)[(num[0] - 1):num[1]] 

63 else: 

64 if num < 1 or num > rows*cols: 

65 raise ValueError( 

66 f"num must be 1 <= num <= {rows*cols}, not {num}") 

67 self._subplotspec = GridSpec( 

68 rows, cols, figure=self.figure)[int(num) - 1] 

69 # num - 1 for converting from MATLAB to python indexing 

70 else: 

71 raise ValueError(f'Illegal argument(s) to subplot: {args}') 

72 

73 self.update_params() 

74 

75 # _axes_class is set in the subplot_class_factory 

76 self._axes_class.__init__(self, fig, self.figbox, **kwargs) 

77 # add a layout box to this, for both the full axis, and the poss 

78 # of the axis. We need both because the axes may become smaller 

79 # due to parasitic axes and hence no longer fill the subplotspec. 

80 if self._subplotspec._layoutbox is None: 

81 self._layoutbox = None 

82 self._poslayoutbox = None 

83 else: 

84 name = self._subplotspec._layoutbox.name + '.ax' 

85 name = name + layoutbox.seq_id() 

86 self._layoutbox = layoutbox.LayoutBox( 

87 parent=self._subplotspec._layoutbox, 

88 name=name, 

89 artist=self) 

90 self._poslayoutbox = layoutbox.LayoutBox( 

91 parent=self._layoutbox, 

92 name=self._layoutbox.name+'.pos', 

93 pos=True, subplot=True, artist=self) 

94 

95 def __reduce__(self): 

96 # get the first axes class which does not inherit from a subplotbase 

97 axes_class = next( 

98 c for c in type(self).__mro__ 

99 if issubclass(c, Axes) and not issubclass(c, SubplotBase)) 

100 return (_picklable_subplot_class_constructor, 

101 (axes_class,), 

102 self.__getstate__()) 

103 

104 def get_geometry(self): 

105 """Get the subplot geometry, e.g., (2, 2, 3).""" 

106 rows, cols, num1, num2 = self.get_subplotspec().get_geometry() 

107 return rows, cols, num1 + 1 # for compatibility 

108 

109 # COVERAGE NOTE: Never used internally or from examples 

110 def change_geometry(self, numrows, numcols, num): 

111 """Change subplot geometry, e.g., from (1, 1, 1) to (2, 2, 3).""" 

112 self._subplotspec = GridSpec(numrows, numcols, 

113 figure=self.figure)[num - 1] 

114 self.update_params() 

115 self.set_position(self.figbox) 

116 

117 def get_subplotspec(self): 

118 """get the SubplotSpec instance associated with the subplot""" 

119 return self._subplotspec 

120 

121 def set_subplotspec(self, subplotspec): 

122 """set the SubplotSpec instance associated with the subplot""" 

123 self._subplotspec = subplotspec 

124 

125 def get_gridspec(self): 

126 """get the GridSpec instance associated with the subplot""" 

127 return self._subplotspec.get_gridspec() 

128 

129 def update_params(self): 

130 """update the subplot position from fig.subplotpars""" 

131 self.figbox, _, _, self.numRows, self.numCols = \ 

132 self.get_subplotspec().get_position(self.figure, 

133 return_all=True) 

134 

135 @cbook.deprecated("3.2", alternative="ax.get_subplotspec().rowspan.start") 

136 @property 

137 def rowNum(self): 

138 return self.get_subplotspec().rowspan.start 

139 

140 @cbook.deprecated("3.2", alternative="ax.get_subplotspec().colspan.start") 

141 @property 

142 def colNum(self): 

143 return self.get_subplotspec().colspan.start 

144 

145 def is_first_row(self): 

146 return self.get_subplotspec().rowspan.start == 0 

147 

148 def is_last_row(self): 

149 return self.get_subplotspec().rowspan.stop == self.get_gridspec().nrows 

150 

151 def is_first_col(self): 

152 return self.get_subplotspec().colspan.start == 0 

153 

154 def is_last_col(self): 

155 return self.get_subplotspec().colspan.stop == self.get_gridspec().ncols 

156 

157 def label_outer(self): 

158 """ 

159 Only show "outer" labels and tick labels. 

160 

161 x-labels are only kept for subplots on the last row; y-labels only for 

162 subplots on the first column. 

163 """ 

164 lastrow = self.is_last_row() 

165 firstcol = self.is_first_col() 

166 if not lastrow: 

167 for label in self.get_xticklabels(which="both"): 

168 label.set_visible(False) 

169 self.get_xaxis().get_offset_text().set_visible(False) 

170 self.set_xlabel("") 

171 if not firstcol: 

172 for label in self.get_yticklabels(which="both"): 

173 label.set_visible(False) 

174 self.get_yaxis().get_offset_text().set_visible(False) 

175 self.set_ylabel("") 

176 

177 def _make_twin_axes(self, *args, **kwargs): 

178 """Make a twinx axes of self. This is used for twinx and twiny.""" 

179 if 'sharex' in kwargs and 'sharey' in kwargs: 

180 # The following line is added in v2.2 to avoid breaking Seaborn, 

181 # which currently uses this internal API. 

182 if kwargs["sharex"] is not self and kwargs["sharey"] is not self: 

183 raise ValueError("Twinned Axes may share only one axis") 

184 # The dance here with label is to force add_subplot() to create a new 

185 # Axes (by passing in a label never seen before). Note that this does 

186 # not affect plot reactivation by subplot() as twin axes can never be 

187 # reactivated by subplot(). 

188 sentinel = str(uuid.uuid4()) 

189 real_label = kwargs.pop("label", sentinel) 

190 twin = self.figure.add_subplot( 

191 self.get_subplotspec(), *args, label=sentinel, **kwargs) 

192 if real_label is not sentinel: 

193 twin.set_label(real_label) 

194 self.set_adjustable('datalim') 

195 twin.set_adjustable('datalim') 

196 if self._layoutbox is not None and twin._layoutbox is not None: 

197 # make the layout boxes be explicitly the same 

198 twin._layoutbox.constrain_same(self._layoutbox) 

199 twin._poslayoutbox.constrain_same(self._poslayoutbox) 

200 self._twinned_axes.join(self, twin) 

201 return twin 

202 

203 

204# this here to support cartopy which was using a private part of the 

205# API to register their Axes subclasses. 

206 

207# In 3.1 this should be changed to a dict subclass that warns on use 

208# In 3.3 to a dict subclass that raises a useful exception on use 

209# In 3.4 should be removed 

210 

211# The slow timeline is to give cartopy enough time to get several 

212# release out before we break them. 

213_subplot_classes = {} 

214 

215 

216@functools.lru_cache(None) 

217def subplot_class_factory(axes_class=None): 

218 """ 

219 This makes a new class that inherits from `.SubplotBase` and the 

220 given axes_class (which is assumed to be a subclass of `.axes.Axes`). 

221 This is perhaps a little bit roundabout to make a new class on 

222 the fly like this, but it means that a new Subplot class does 

223 not have to be created for every type of Axes. 

224 """ 

225 if axes_class is None: 

226 axes_class = Axes 

227 try: 

228 # Avoid creating two different instances of GeoAxesSubplot... 

229 # Only a temporary backcompat fix. This should be removed in 

230 # 3.4 

231 return next(cls for cls in SubplotBase.__subclasses__() 

232 if cls.__bases__ == (SubplotBase, axes_class)) 

233 except StopIteration: 

234 return type("%sSubplot" % axes_class.__name__, 

235 (SubplotBase, axes_class), 

236 {'_axes_class': axes_class}) 

237 

238 

239# This is provided for backward compatibility 

240Subplot = subplot_class_factory() 

241 

242 

243def _picklable_subplot_class_constructor(axes_class): 

244 """ 

245 This stub class exists to return the appropriate subplot class when called 

246 with an axes class. This is purely to allow pickling of Axes and Subplots. 

247 """ 

248 subplot_class = subplot_class_factory(axes_class) 

249 return subplot_class.__new__(subplot_class) 

250 

251 

252docstring.interpd.update(Axes=martist.kwdoc(Axes)) 

253docstring.dedent_interpd(Axes.__init__) 

254 

255docstring.interpd.update(Subplot=martist.kwdoc(Axes))