Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Newer
Older
100644 331 lines (245 sloc) 11.475 kb
9246445 @leejjoon add tight_layout functionality
leejjoon authored
1 """
2 This module provides routines to adjust subplot params so that
3 subplots are nicely fit in the figure. In doing so, only axis labels,
4 tick labels and axes titles are currently considered.
5
6 Internally, it assumes that the margins (left_margin, etc.) which are
7 differences between ax.get_tightbbox and ax.bbox are independent of
8 axes position. This may fail if Axes.adjustable is datalim. Also, This
9 will fail for some cases (for example, left or right margin is affected by xlabel).
10
11 """
12
1103385 @leejjoon minor modifications to make more py3 friendly
leejjoon authored
13 import warnings
14
9246445 @leejjoon add tight_layout functionality
leejjoon authored
15 import matplotlib
e08c2ff @leejjoon tight_layout supports axes_grid1 toolkits
leejjoon authored
16 from matplotlib.transforms import TransformedBbox, Bbox
9246445 @leejjoon add tight_layout functionality
leejjoon authored
17
18 from matplotlib.font_manager import FontProperties
19 rcParams = matplotlib.rcParams
20
21
22
23 def _get_left(tight_bbox, axes_bbox):
24 return axes_bbox.xmin - tight_bbox.xmin
25
26 def _get_right(tight_bbox, axes_bbox):
27 return tight_bbox.xmax - axes_bbox.xmax
28
29 def _get_bottom(tight_bbox, axes_bbox):
30 return axes_bbox.ymin - tight_bbox.ymin
31
32 def _get_top(tight_bbox, axes_bbox):
33 return tight_bbox.ymax - axes_bbox.ymax
34
35
36 def auto_adjust_subplotpars(fig, renderer,
37 nrows_ncols,
38 num1num2_list,
39 subplot_list,
e08c2ff @leejjoon tight_layout supports axes_grid1 toolkits
leejjoon authored
40 ax_bbox_list=None,
754535b restore previous tight_layout padding results by correcting the defaults
pwuertz authored
41 pad=1.08, h_pad=None, w_pad=None,
9246445 @leejjoon add tight_layout functionality
leejjoon authored
42 rect=None):
43 """
44 Return a dictionary of subplot parameters so that spacing between
45 subplots are adjusted. Note that this function ignore geometry
46 information of subplot itself, but uses what is given by
2b7ce9e @jdh2358 * Remove matplotlib.legend from artist_api.rst since it is document i…
jdh2358 authored
47 *nrows_ncols* and *num1num2_list* parameteres. Also, the results could be
48 incorrect if some subplots have ``adjustable=datalim``.
1103385 @leejjoon minor modifications to make more py3 friendly
leejjoon authored
49
2b7ce9e @jdh2358 * Remove matplotlib.legend from artist_api.rst since it is document i…
jdh2358 authored
50 Parameters:
51
52 nrows_ncols
53 number of rows and number of columns of the grid.
54
55 num1num2_list
56 list of numbers specifying the area occupied by the subplot
57
58 subplot_list
59 list of subplots that will be used to calcuate optimal subplot_params.
60
9246445 @leejjoon add tight_layout functionality
leejjoon authored
61 pad : float
2b7ce9e @jdh2358 * Remove matplotlib.legend from artist_api.rst since it is document i…
jdh2358 authored
62 padding between the figure edge and the edges of subplots, as a fraction of the font-size.
9246445 @leejjoon add tight_layout functionality
leejjoon authored
63 h_pad, w_pad : float
2b7ce9e @jdh2358 * Remove matplotlib.legend from artist_api.rst since it is document i…
jdh2358 authored
64 padding (height/width) between edges of adjacent subplots.
9246445 @leejjoon add tight_layout functionality
leejjoon authored
65 Defaults to `pad_inches`.
2b7ce9e @jdh2358 * Remove matplotlib.legend from artist_api.rst since it is document i…
jdh2358 authored
66
67 rect
68 [left, bottom, right, top] in normalized (0, 1) figure coordinates.
9246445 @leejjoon add tight_layout functionality
leejjoon authored
69 """
70
71
72 rows, cols = nrows_ncols
73
74093a6 fixed conversion from pt to inch in tight_layout
pwuertz authored
74 pad_inches = pad * FontProperties(size=rcParams["font.size"]).get_size_in_points() / 72.
9246445 @leejjoon add tight_layout functionality
leejjoon authored
75
76 if h_pad is not None:
74093a6 fixed conversion from pt to inch in tight_layout
pwuertz authored
77 vpad_inches = h_pad * FontProperties(size=rcParams["font.size"]).get_size_in_points() / 72.
9246445 @leejjoon add tight_layout functionality
leejjoon authored
78 else:
79 vpad_inches = pad_inches
80
81 if w_pad is not None:
74093a6 fixed conversion from pt to inch in tight_layout
pwuertz authored
82 hpad_inches = w_pad * FontProperties(size=rcParams["font.size"]).get_size_in_points() / 72.
9246445 @leejjoon add tight_layout functionality
leejjoon authored
83 else:
84 hpad_inches = pad_inches
1103385 @leejjoon minor modifications to make more py3 friendly
leejjoon authored
85
9246445 @leejjoon add tight_layout functionality
leejjoon authored
86
87 if len(subplot_list) == 0:
88 raise RuntimeError("")
89
90 if len(num1num2_list) != len(subplot_list):
91 raise RuntimeError("")
92
93 if rect is None:
94 margin_left, margin_bottom, margin_right, margin_top = None, None, None, None
95 else:
96 margin_left, margin_bottom, _right, _top = rect
97 if _right: margin_right = 1. - _right
98 else: margin_right = None
99 if _top: margin_top = 1. - _top
100 else: margin_top = None
1103385 @leejjoon minor modifications to make more py3 friendly
leejjoon authored
101
9246445 @leejjoon add tight_layout functionality
leejjoon authored
102 vspaces = [[] for i in range((rows+1)*cols)]
103 hspaces = [[] for i in range(rows*(cols+1))]
1103385 @leejjoon minor modifications to make more py3 friendly
leejjoon authored
104
e08c2ff @leejjoon tight_layout supports axes_grid1 toolkits
leejjoon authored
105 union = Bbox.union
9246445 @leejjoon add tight_layout functionality
leejjoon authored
106
e08c2ff @leejjoon tight_layout supports axes_grid1 toolkits
leejjoon authored
107 if ax_bbox_list is None:
108 ax_bbox_list = []
109 for subplots in subplot_list:
110 ax_bbox = union([ax.get_position(original=True) for ax in subplots])
111 ax_bbox_list.append(ax_bbox)
1103385 @leejjoon minor modifications to make more py3 friendly
leejjoon authored
112
e08c2ff @leejjoon tight_layout supports axes_grid1 toolkits
leejjoon authored
113 for subplots, ax_bbox, (num1, num2) in zip(subplot_list,
114 ax_bbox_list,
115 num1num2_list):
116
117 #ax_bbox = union([ax.get_position(original=True) for ax in subplots])
9246445 @leejjoon add tight_layout functionality
leejjoon authored
118
e08c2ff @leejjoon tight_layout supports axes_grid1 toolkits
leejjoon authored
119 tight_bbox_raw = union([ax.get_tightbbox(renderer) for ax in subplots])
9246445 @leejjoon add tight_layout functionality
leejjoon authored
120 tight_bbox = TransformedBbox(tight_bbox_raw, fig.transFigure.inverted())
121
122 row1, col1 = divmod(num1, cols)
123
124
125 if num2 is None:
126 # left
127 hspaces[row1 * (cols+1) + col1].append(_get_left(tight_bbox, ax_bbox))
128 # right
129 hspaces[row1 * (cols+1) + (col1+1)].append(_get_right(tight_bbox, ax_bbox))
130 # top
131 vspaces[row1 * cols + col1].append(_get_top(tight_bbox, ax_bbox))
132 # bottom
133 vspaces[(row1+1) * cols + col1].append(_get_bottom(tight_bbox, ax_bbox))
134
135 else:
136 row2, col2 = divmod(num2, cols)
1103385 @leejjoon minor modifications to make more py3 friendly
leejjoon authored
137
9246445 @leejjoon add tight_layout functionality
leejjoon authored
138 for row_i in range(row1, row2+1):
139 # left
140 hspaces[row_i * (cols+1) + col1].append(_get_left(tight_bbox, ax_bbox))
141 # right
142 hspaces[row_i * (cols+1) + (col2+1)].append(_get_right(tight_bbox, ax_bbox))
143 for col_i in range(col1, col2+1):
144 # top
145 vspaces[row1 * cols + col_i].append(_get_top(tight_bbox, ax_bbox))
146 # bottom
147 vspaces[(row2+1) * cols + col_i].append(_get_bottom(tight_bbox, ax_bbox))
148
149
150 fig_width_inch, fig_height_inch = fig.get_size_inches()
c7bd896 @leejjoon tight_layout.py : some fix for axes with aspect!=auto
leejjoon authored
151
152 # margins can be negative for axes with aspect applied. And we
153 # append + [0] to make minimum margins 0
154
9246445 @leejjoon add tight_layout functionality
leejjoon authored
155 if not margin_left:
c7bd896 @leejjoon tight_layout.py : some fix for axes with aspect!=auto
leejjoon authored
156 margin_left = max([sum(s) for s in hspaces[::cols+1]] + [0])
9246445 @leejjoon add tight_layout functionality
leejjoon authored
157 margin_left += pad_inches / fig_width_inch
1103385 @leejjoon minor modifications to make more py3 friendly
leejjoon authored
158
9246445 @leejjoon add tight_layout functionality
leejjoon authored
159 if not margin_right:
c7bd896 @leejjoon tight_layout.py : some fix for axes with aspect!=auto
leejjoon authored
160 margin_right = max([sum(s) for s in hspaces[cols::cols+1]] + [0])
9246445 @leejjoon add tight_layout functionality
leejjoon authored
161 margin_right += pad_inches / fig_width_inch
162
163 if not margin_top:
c7bd896 @leejjoon tight_layout.py : some fix for axes with aspect!=auto
leejjoon authored
164 margin_top = max([sum(s) for s in vspaces[:cols]] + [0])
9246445 @leejjoon add tight_layout functionality
leejjoon authored
165 margin_top += pad_inches / fig_height_inch
166
167 if not margin_bottom:
c7bd896 @leejjoon tight_layout.py : some fix for axes with aspect!=auto
leejjoon authored
168 margin_bottom = max([sum(s) for s in vspaces[-cols:]] + [0])
9246445 @leejjoon add tight_layout functionality
leejjoon authored
169 margin_bottom += pad_inches / fig_height_inch
1103385 @leejjoon minor modifications to make more py3 friendly
leejjoon authored
170
9246445 @leejjoon add tight_layout functionality
leejjoon authored
171
c7bd896 @leejjoon tight_layout.py : some fix for axes with aspect!=auto
leejjoon authored
172 kwargs = dict(left=margin_left,
173 right=1-margin_right,
174 bottom=margin_bottom,
175 top=1-margin_top)
9246445 @leejjoon add tight_layout functionality
leejjoon authored
176
177 if cols > 1:
178 hspace = max([sum(s) for i in range(rows) for s in hspaces[i*(cols+1)+1:(i+1)*(cols+1)-1]])
179 hspace += hpad_inches / fig_width_inch
180 h_axes = ((1-margin_right-margin_left) - hspace * (cols - 1))/cols
181
182 kwargs["wspace"]=hspace/h_axes
183
184 if rows > 1:
185 vspace = max([sum(s) for s in vspaces[cols:-cols]])
186 vspace += vpad_inches / fig_height_inch
187 v_axes = ((1-margin_top-margin_bottom) - vspace * (rows - 1))/rows
188
189 kwargs["hspace"]=vspace/v_axes
190
191 return kwargs
1103385 @leejjoon minor modifications to make more py3 friendly
leejjoon authored
192
9246445 @leejjoon add tight_layout functionality
leejjoon authored
193
194 def get_renderer(fig):
195 if fig._cachedRenderer:
196 renderer = fig._cachedRenderer
197 else:
198 canvas = fig.canvas
199
200 if canvas and hasattr(canvas, "get_renderer"):
201 renderer = canvas.get_renderer()
202 else:
203 # not sure if this can happen
1103385 @leejjoon minor modifications to make more py3 friendly
leejjoon authored
204 warnings.warn("tight_layout : falling back to Agg renderer")
9246445 @leejjoon add tight_layout functionality
leejjoon authored
205 from matplotlib.backends.backend_agg import FigureCanvasAgg
206 canvas = FigureCanvasAgg(fig)
207 renderer = canvas.get_renderer()
208
209 return renderer
5154655 @leejjoon made Figure.tight_layout take the rect parameter and did some refacto…
leejjoon authored
210
211
212 def get_tight_layout_figure(fig, axes_list, renderer,
754535b restore previous tight_layout padding results by correcting the defaults
pwuertz authored
213 pad=1.08, h_pad=None, w_pad=None, rect=None):
5154655 @leejjoon made Figure.tight_layout take the rect parameter and did some refacto…
leejjoon authored
214 """
0807d24 @mdboom Fix documentation warnings
mdboom authored
215 Return subplot parameters for tight-layouted-figure with specified
216 padding.
5154655 @leejjoon made Figure.tight_layout take the rect parameter and did some refacto…
leejjoon authored
217
218 Parameters:
219
220 *fig* : figure instance
0807d24 @mdboom Fix documentation warnings
mdboom authored
221
5154655 @leejjoon made Figure.tight_layout take the rect parameter and did some refacto…
leejjoon authored
222 *axes_list* : a list of axes
0807d24 @mdboom Fix documentation warnings
mdboom authored
223
5154655 @leejjoon made Figure.tight_layout take the rect parameter and did some refacto…
leejjoon authored
224 *renderer* : renderer instance
0807d24 @mdboom Fix documentation warnings
mdboom authored
225
5154655 @leejjoon made Figure.tight_layout take the rect parameter and did some refacto…
leejjoon authored
226 *pad* : float
227 padding between the figure edge and the edges of subplots,
228 as a fraction of the font-size.
0807d24 @mdboom Fix documentation warnings
mdboom authored
229
5154655 @leejjoon made Figure.tight_layout take the rect parameter and did some refacto…
leejjoon authored
230 *h_pad*, *w_pad* : float
231 padding (height/width) between edges of adjacent subplots.
232 Defaults to `pad_inches`.
0807d24 @mdboom Fix documentation warnings
mdboom authored
233
5154655 @leejjoon made Figure.tight_layout take the rect parameter and did some refacto…
leejjoon authored
234 *rect* : if rect is given, it is interpreted as a rectangle
235 (left, bottom, right, top) in the normalized figure
236 coordinate that the whole subplots area (including
237 labels) will fit into. Default is (0, 0, 1, 1).
238 """
239
240
241 subplotspec_list = []
242 subplot_list = []
243 nrows_list = []
244 ncols_list = []
245 ax_bbox_list = []
246
247 subplot_dict = {} # for axes_grid1, multiple axes can share
248 # same subplot_interface. Thus we need to
249 # join them together.
250
251 for ax in axes_list:
252 locator = ax.get_axes_locator()
253 if hasattr(locator, "get_subplotspec"):
254 subplotspec = locator.get_subplotspec().get_topmost_subplotspec()
255 elif hasattr(ax, "get_subplotspec"):
256 subplotspec = ax.get_subplotspec().get_topmost_subplotspec()
257 else:
258 continue
259
260 if (subplotspec is None) or \
261 subplotspec.get_gridspec().locally_modified_subplot_params():
262 continue
263
264 subplots = subplot_dict.setdefault(subplotspec, [])
265
266 if not subplots:
267 myrows, mycols, _, _ = subplotspec.get_geometry()
268 nrows_list.append(myrows)
269 ncols_list.append(mycols)
270 subplotspec_list.append(subplotspec)
271 subplot_list.append(subplots)
272 ax_bbox_list.append(subplotspec.get_position(fig))
273
274 subplots.append(ax)
275
276 max_nrows = max(nrows_list)
277 max_ncols = max(ncols_list)
278
279 num1num2_list = []
280 for subplotspec in subplotspec_list:
281 rows, cols, num1, num2 = subplotspec.get_geometry()
282 div_row, mod_row = divmod(max_nrows, rows)
283 div_col, mod_col = divmod(max_ncols, cols)
284 if (mod_row != 0) or (mod_col != 0):
285 raise RuntimeError("")
286
287 rowNum1, colNum1 = divmod(num1, cols)
288 if num2 is None:
289 rowNum2, colNum2 = rowNum1, colNum1
290 else:
291 rowNum2, colNum2 = divmod(num2, cols)
292
293 num1num2_list.append((rowNum1*div_row*max_ncols + colNum1*div_col,
294 ((rowNum2+1)*div_row-1)*max_ncols + (colNum2+1)*div_col-1))
295
296
297 kwargs = auto_adjust_subplotpars(fig, renderer,
298 nrows_ncols=(max_nrows, max_ncols),
299 num1num2_list=num1num2_list,
300 subplot_list=subplot_list,
301 ax_bbox_list=ax_bbox_list,
302 pad=pad, h_pad=h_pad, w_pad=w_pad)
303
304 if rect is not None:
305 # if rect is given, the whole subplots area (including
306 # labels) will fit into the rect instead of the
307 # figure. Note that the rect argument of
308 # *auto_adjust_subplotpars* specify the area that will be
309 # covered by the total area of axes.bbox. Thus we call
310 # auto_adjust_subplotpars twice, where the second run
311 # with adjusted rect parameters.
312
313 left, bottom, right, top = rect
314 if left is not None: left += kwargs["left"]
315 if bottom is not None: bottom += kwargs["bottom"]
316 if right is not None: right -= (1 - kwargs["right"])
317 if top is not None: top -= (1 - kwargs["top"])
318
319 #if h_pad is None: h_pad = pad
320 #if w_pad is None: w_pad = pad
321
322 kwargs = auto_adjust_subplotpars(fig, renderer,
323 nrows_ncols=(max_nrows, max_ncols),
324 num1num2_list=num1num2_list,
325 subplot_list=subplot_list,
326 ax_bbox_list=ax_bbox_list,
327 pad=pad, h_pad=h_pad, w_pad=w_pad,
328 rect=(left, bottom, right, top))
329
330 return kwargs
Something went wrong with that request. Please try again.