Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.9/dist-packages/matplotlib/layout_engine.py: 43%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

69 statements  

1""" 

2Classes to layout elements in a `.Figure`. 

3 

4Figures have a ``layout_engine`` property that holds a subclass of 

5`~.LayoutEngine` defined here (or *None* for no layout). At draw time 

6``figure.get_layout_engine().execute()`` is called, the goal of which is 

7usually to rearrange Axes on the figure to produce a pleasing layout. This is 

8like a ``draw`` callback but with two differences. First, when printing we 

9disable the layout engine for the final draw. Second, it is useful to know the 

10layout engine while the figure is being created. In particular, colorbars are 

11made differently with different layout engines (for historical reasons). 

12 

13Matplotlib supplies two layout engines, `.TightLayoutEngine` and 

14`.ConstrainedLayoutEngine`. Third parties can create their own layout engine 

15by subclassing `.LayoutEngine`. 

16""" 

17 

18from contextlib import nullcontext 

19 

20import matplotlib as mpl 

21 

22from matplotlib._constrained_layout import do_constrained_layout 

23from matplotlib._tight_layout import (get_subplotspec_list, 

24 get_tight_layout_figure) 

25 

26 

27class LayoutEngine: 

28 """ 

29 Base class for Matplotlib layout engines. 

30 

31 A layout engine can be passed to a figure at instantiation or at any time 

32 with `~.figure.Figure.set_layout_engine`. Once attached to a figure, the 

33 layout engine ``execute`` function is called at draw time by 

34 `~.figure.Figure.draw`, providing a special draw-time hook. 

35 

36 .. note:: 

37 

38 However, note that layout engines affect the creation of colorbars, so 

39 `~.figure.Figure.set_layout_engine` should be called before any 

40 colorbars are created. 

41 

42 Currently, there are two properties of `LayoutEngine` classes that are 

43 consulted while manipulating the figure: 

44 

45 - ``engine.colorbar_gridspec`` tells `.Figure.colorbar` whether to make the 

46 axes using the gridspec method (see `.colorbar.make_axes_gridspec`) or 

47 not (see `.colorbar.make_axes`); 

48 - ``engine.adjust_compatible`` stops `.Figure.subplots_adjust` from being 

49 run if it is not compatible with the layout engine. 

50 

51 To implement a custom `LayoutEngine`: 

52 

53 1. override ``_adjust_compatible`` and ``_colorbar_gridspec`` 

54 2. override `LayoutEngine.set` to update *self._params* 

55 3. override `LayoutEngine.execute` with your implementation 

56 

57 """ 

58 # override these in subclass 

59 _adjust_compatible = None 

60 _colorbar_gridspec = None 

61 

62 def __init__(self, **kwargs): 

63 super().__init__(**kwargs) 

64 self._params = {} 

65 

66 def set(self, **kwargs): 

67 """ 

68 Set the parameters for the layout engine. 

69 """ 

70 raise NotImplementedError 

71 

72 @property 

73 def colorbar_gridspec(self): 

74 """ 

75 Return a boolean if the layout engine creates colorbars using a 

76 gridspec. 

77 """ 

78 if self._colorbar_gridspec is None: 

79 raise NotImplementedError 

80 return self._colorbar_gridspec 

81 

82 @property 

83 def adjust_compatible(self): 

84 """ 

85 Return a boolean if the layout engine is compatible with 

86 `~.Figure.subplots_adjust`. 

87 """ 

88 if self._adjust_compatible is None: 

89 raise NotImplementedError 

90 return self._adjust_compatible 

91 

92 def get(self): 

93 """ 

94 Return copy of the parameters for the layout engine. 

95 """ 

96 return dict(self._params) 

97 

98 def execute(self, fig): 

99 """ 

100 Execute the layout on the figure given by *fig*. 

101 """ 

102 # subclasses must implement this. 

103 raise NotImplementedError 

104 

105 

106class PlaceHolderLayoutEngine(LayoutEngine): 

107 """ 

108 This layout engine does not adjust the figure layout at all. 

109 

110 The purpose of this `.LayoutEngine` is to act as a placeholder when the user removes 

111 a layout engine to ensure an incompatible `.LayoutEngine` cannot be set later. 

112 

113 Parameters 

114 ---------- 

115 adjust_compatible, colorbar_gridspec : bool 

116 Allow the PlaceHolderLayoutEngine to mirror the behavior of whatever 

117 layout engine it is replacing. 

118 

119 """ 

120 def __init__(self, adjust_compatible, colorbar_gridspec, **kwargs): 

121 self._adjust_compatible = adjust_compatible 

122 self._colorbar_gridspec = colorbar_gridspec 

123 super().__init__(**kwargs) 

124 

125 def execute(self, fig): 

126 """ 

127 Do nothing. 

128 """ 

129 return 

130 

131 

132class TightLayoutEngine(LayoutEngine): 

133 """ 

134 Implements the ``tight_layout`` geometry management. See 

135 :ref:`tight_layout_guide` for details. 

136 """ 

137 _adjust_compatible = True 

138 _colorbar_gridspec = True 

139 

140 def __init__(self, *, pad=1.08, h_pad=None, w_pad=None, 

141 rect=(0, 0, 1, 1), **kwargs): 

142 """ 

143 Initialize tight_layout engine. 

144 

145 Parameters 

146 ---------- 

147 pad : float, default: 1.08 

148 Padding between the figure edge and the edges of subplots, as a 

149 fraction of the font size. 

150 h_pad, w_pad : float 

151 Padding (height/width) between edges of adjacent subplots. 

152 Defaults to *pad*. 

153 rect : tuple (left, bottom, right, top), default: (0, 0, 1, 1). 

154 rectangle in normalized figure coordinates that the subplots 

155 (including labels) will fit into. 

156 """ 

157 super().__init__(**kwargs) 

158 for td in ['pad', 'h_pad', 'w_pad', 'rect']: 

159 # initialize these in case None is passed in above: 

160 self._params[td] = None 

161 self.set(pad=pad, h_pad=h_pad, w_pad=w_pad, rect=rect) 

162 

163 def execute(self, fig): 

164 """ 

165 Execute tight_layout. 

166 

167 This decides the subplot parameters given the padding that 

168 will allow the Axes labels to not be covered by other labels 

169 and Axes. 

170 

171 Parameters 

172 ---------- 

173 fig : `.Figure` to perform layout on. 

174 

175 See Also 

176 -------- 

177 .figure.Figure.tight_layout 

178 .pyplot.tight_layout 

179 """ 

180 info = self._params 

181 renderer = fig._get_renderer() 

182 with getattr(renderer, "_draw_disabled", nullcontext)(): 

183 kwargs = get_tight_layout_figure( 

184 fig, fig.axes, get_subplotspec_list(fig.axes), renderer, 

185 pad=info['pad'], h_pad=info['h_pad'], w_pad=info['w_pad'], 

186 rect=info['rect']) 

187 if kwargs: 

188 fig.subplots_adjust(**kwargs) 

189 

190 def set(self, *, pad=None, w_pad=None, h_pad=None, rect=None): 

191 """ 

192 Set the pads for tight_layout. 

193 

194 Parameters 

195 ---------- 

196 pad : float 

197 Padding between the figure edge and the edges of subplots, as a 

198 fraction of the font size. 

199 w_pad, h_pad : float 

200 Padding (width/height) between edges of adjacent subplots. 

201 Defaults to *pad*. 

202 rect : tuple (left, bottom, right, top) 

203 rectangle in normalized figure coordinates that the subplots 

204 (including labels) will fit into. 

205 """ 

206 for td in self.set.__kwdefaults__: 

207 if locals()[td] is not None: 

208 self._params[td] = locals()[td] 

209 

210 

211class ConstrainedLayoutEngine(LayoutEngine): 

212 """ 

213 Implements the ``constrained_layout`` geometry management. See 

214 :ref:`constrainedlayout_guide` for details. 

215 """ 

216 

217 _adjust_compatible = False 

218 _colorbar_gridspec = False 

219 

220 def __init__(self, *, h_pad=None, w_pad=None, 

221 hspace=None, wspace=None, rect=(0, 0, 1, 1), 

222 compress=False, **kwargs): 

223 """ 

224 Initialize ``constrained_layout`` settings. 

225 

226 Parameters 

227 ---------- 

228 h_pad, w_pad : float 

229 Padding around the Axes elements in inches. 

230 Default to :rc:`figure.constrained_layout.h_pad` and 

231 :rc:`figure.constrained_layout.w_pad`. 

232 hspace, wspace : float 

233 Fraction of the figure to dedicate to space between the 

234 axes. These are evenly spread between the gaps between the Axes. 

235 A value of 0.2 for a three-column layout would have a space 

236 of 0.1 of the figure width between each column. 

237 If h/wspace < h/w_pad, then the pads are used instead. 

238 Default to :rc:`figure.constrained_layout.hspace` and 

239 :rc:`figure.constrained_layout.wspace`. 

240 rect : tuple of 4 floats 

241 Rectangle in figure coordinates to perform constrained layout in 

242 (left, bottom, width, height), each from 0-1. 

243 compress : bool 

244 Whether to shift Axes so that white space in between them is 

245 removed. This is useful for simple grids of fixed-aspect Axes (e.g. 

246 a grid of images). See :ref:`compressed_layout`. 

247 """ 

248 super().__init__(**kwargs) 

249 # set the defaults: 

250 self.set(w_pad=mpl.rcParams['figure.constrained_layout.w_pad'], 

251 h_pad=mpl.rcParams['figure.constrained_layout.h_pad'], 

252 wspace=mpl.rcParams['figure.constrained_layout.wspace'], 

253 hspace=mpl.rcParams['figure.constrained_layout.hspace'], 

254 rect=(0, 0, 1, 1)) 

255 # set anything that was passed in (None will be ignored): 

256 self.set(w_pad=w_pad, h_pad=h_pad, wspace=wspace, hspace=hspace, 

257 rect=rect) 

258 self._compress = compress 

259 

260 def execute(self, fig): 

261 """ 

262 Perform constrained_layout and move and resize Axes accordingly. 

263 

264 Parameters 

265 ---------- 

266 fig : `.Figure` to perform layout on. 

267 """ 

268 width, height = fig.get_size_inches() 

269 # pads are relative to the current state of the figure... 

270 w_pad = self._params['w_pad'] / width 

271 h_pad = self._params['h_pad'] / height 

272 

273 return do_constrained_layout(fig, w_pad=w_pad, h_pad=h_pad, 

274 wspace=self._params['wspace'], 

275 hspace=self._params['hspace'], 

276 rect=self._params['rect'], 

277 compress=self._compress) 

278 

279 def set(self, *, h_pad=None, w_pad=None, 

280 hspace=None, wspace=None, rect=None): 

281 """ 

282 Set the pads for constrained_layout. 

283 

284 Parameters 

285 ---------- 

286 h_pad, w_pad : float 

287 Padding around the Axes elements in inches. 

288 Default to :rc:`figure.constrained_layout.h_pad` and 

289 :rc:`figure.constrained_layout.w_pad`. 

290 hspace, wspace : float 

291 Fraction of the figure to dedicate to space between the 

292 axes. These are evenly spread between the gaps between the Axes. 

293 A value of 0.2 for a three-column layout would have a space 

294 of 0.1 of the figure width between each column. 

295 If h/wspace < h/w_pad, then the pads are used instead. 

296 Default to :rc:`figure.constrained_layout.hspace` and 

297 :rc:`figure.constrained_layout.wspace`. 

298 rect : tuple of 4 floats 

299 Rectangle in figure coordinates to perform constrained layout in 

300 (left, bottom, width, height), each from 0-1. 

301 """ 

302 for td in self.set.__kwdefaults__: 

303 if locals()[td] is not None: 

304 self._params[td] = locals()[td]