test_constrainedlayout.py 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743
  1. import gc
  2. import platform
  3. import numpy as np
  4. import pytest
  5. import matplotlib as mpl
  6. from matplotlib.testing.decorators import image_comparison
  7. import matplotlib.pyplot as plt
  8. import matplotlib.transforms as mtransforms
  9. from matplotlib import gridspec, ticker
  10. pytestmark = [
  11. pytest.mark.usefixtures('text_placeholders')
  12. ]
  13. def example_plot(ax, fontsize=12, nodec=False):
  14. ax.plot([1, 2])
  15. ax.locator_params(nbins=3)
  16. if not nodec:
  17. ax.set_xlabel('x-label', fontsize=fontsize)
  18. ax.set_ylabel('y-label', fontsize=fontsize)
  19. ax.set_title('Title', fontsize=fontsize)
  20. else:
  21. ax.set_xticklabels([])
  22. ax.set_yticklabels([])
  23. def example_pcolor(ax, fontsize=12):
  24. dx, dy = 0.6, 0.6
  25. y, x = np.mgrid[slice(-3, 3 + dy, dy),
  26. slice(-3, 3 + dx, dx)]
  27. z = (1 - x / 2. + x ** 5 + y ** 3) * np.exp(-x ** 2 - y ** 2)
  28. pcm = ax.pcolormesh(x, y, z[:-1, :-1], cmap='RdBu_r', vmin=-1., vmax=1.,
  29. rasterized=True)
  30. ax.set_xlabel('x-label', fontsize=fontsize)
  31. ax.set_ylabel('y-label', fontsize=fontsize)
  32. ax.set_title('Title', fontsize=fontsize)
  33. return pcm
  34. @image_comparison(['constrained_layout1.png'], style='mpl20')
  35. def test_constrained_layout1():
  36. """Test constrained_layout for a single subplot"""
  37. fig = plt.figure(layout="constrained")
  38. ax = fig.add_subplot()
  39. example_plot(ax, fontsize=24)
  40. @image_comparison(['constrained_layout2.png'], style='mpl20')
  41. def test_constrained_layout2():
  42. """Test constrained_layout for 2x2 subplots"""
  43. fig, axs = plt.subplots(2, 2, layout="constrained")
  44. for ax in axs.flat:
  45. example_plot(ax, fontsize=24)
  46. @image_comparison(['constrained_layout3.png'], style='mpl20')
  47. def test_constrained_layout3():
  48. """Test constrained_layout for colorbars with subplots"""
  49. fig, axs = plt.subplots(2, 2, layout="constrained")
  50. for nn, ax in enumerate(axs.flat):
  51. pcm = example_pcolor(ax, fontsize=24)
  52. if nn == 3:
  53. pad = 0.08
  54. else:
  55. pad = 0.02 # default
  56. fig.colorbar(pcm, ax=ax, pad=pad)
  57. @image_comparison(['constrained_layout4.png'], style='mpl20')
  58. def test_constrained_layout4():
  59. """Test constrained_layout for a single colorbar with subplots"""
  60. fig, axs = plt.subplots(2, 2, layout="constrained")
  61. for ax in axs.flat:
  62. pcm = example_pcolor(ax, fontsize=24)
  63. fig.colorbar(pcm, ax=axs, pad=0.01, shrink=0.6)
  64. @image_comparison(['constrained_layout5.png'], style='mpl20')
  65. def test_constrained_layout5():
  66. """
  67. Test constrained_layout for a single colorbar with subplots,
  68. colorbar bottom
  69. """
  70. fig, axs = plt.subplots(2, 2, layout="constrained")
  71. for ax in axs.flat:
  72. pcm = example_pcolor(ax, fontsize=24)
  73. fig.colorbar(pcm, ax=axs,
  74. use_gridspec=False, pad=0.01, shrink=0.6,
  75. location='bottom')
  76. @image_comparison(['constrained_layout6.png'], style='mpl20')
  77. def test_constrained_layout6():
  78. """Test constrained_layout for nested gridspecs"""
  79. fig = plt.figure(layout="constrained")
  80. gs = fig.add_gridspec(1, 2, figure=fig)
  81. gsl = gs[0].subgridspec(2, 2)
  82. gsr = gs[1].subgridspec(1, 2)
  83. axsl = []
  84. for gs in gsl:
  85. ax = fig.add_subplot(gs)
  86. axsl += [ax]
  87. example_plot(ax, fontsize=12)
  88. ax.set_xlabel('x-label\nMultiLine')
  89. axsr = []
  90. for gs in gsr:
  91. ax = fig.add_subplot(gs)
  92. axsr += [ax]
  93. pcm = example_pcolor(ax, fontsize=12)
  94. fig.colorbar(pcm, ax=axsr,
  95. pad=0.01, shrink=0.99, location='bottom',
  96. ticks=ticker.MaxNLocator(nbins=5))
  97. def test_identical_subgridspec():
  98. fig = plt.figure(constrained_layout=True)
  99. GS = fig.add_gridspec(2, 1)
  100. GSA = GS[0].subgridspec(1, 3)
  101. GSB = GS[1].subgridspec(1, 3)
  102. axa = []
  103. axb = []
  104. for i in range(3):
  105. axa += [fig.add_subplot(GSA[i])]
  106. axb += [fig.add_subplot(GSB[i])]
  107. fig.draw_without_rendering()
  108. # check first row above second
  109. assert axa[0].get_position().y0 > axb[0].get_position().y1
  110. def test_constrained_layout7():
  111. """Test for proper warning if fig not set in GridSpec"""
  112. with pytest.warns(
  113. UserWarning, match=('There are no gridspecs with layoutgrids. '
  114. 'Possibly did not call parent GridSpec with '
  115. 'the "figure" keyword')):
  116. fig = plt.figure(layout="constrained")
  117. gs = gridspec.GridSpec(1, 2)
  118. gsl = gridspec.GridSpecFromSubplotSpec(2, 2, gs[0])
  119. gsr = gridspec.GridSpecFromSubplotSpec(1, 2, gs[1])
  120. for gs in gsl:
  121. fig.add_subplot(gs)
  122. # need to trigger a draw to get warning
  123. fig.draw_without_rendering()
  124. @image_comparison(['constrained_layout8.png'], style='mpl20')
  125. def test_constrained_layout8():
  126. """Test for gridspecs that are not completely full"""
  127. fig = plt.figure(figsize=(10, 5), layout="constrained")
  128. gs = gridspec.GridSpec(3, 5, figure=fig)
  129. axs = []
  130. for j in [0, 1]:
  131. if j == 0:
  132. ilist = [1]
  133. else:
  134. ilist = [0, 4]
  135. for i in ilist:
  136. ax = fig.add_subplot(gs[j, i])
  137. axs += [ax]
  138. example_pcolor(ax, fontsize=9)
  139. if i > 0:
  140. ax.set_ylabel('')
  141. if j < 1:
  142. ax.set_xlabel('')
  143. ax.set_title('')
  144. ax = fig.add_subplot(gs[2, :])
  145. axs += [ax]
  146. pcm = example_pcolor(ax, fontsize=9)
  147. fig.colorbar(pcm, ax=axs, pad=0.01, shrink=0.6)
  148. @image_comparison(['constrained_layout9.png'], style='mpl20')
  149. def test_constrained_layout9():
  150. """Test for handling suptitle and for sharex and sharey"""
  151. fig, axs = plt.subplots(2, 2, layout="constrained",
  152. sharex=False, sharey=False)
  153. for ax in axs.flat:
  154. pcm = example_pcolor(ax, fontsize=24)
  155. ax.set_xlabel('')
  156. ax.set_ylabel('')
  157. ax.set_aspect(2.)
  158. fig.colorbar(pcm, ax=axs, pad=0.01, shrink=0.6)
  159. fig.suptitle('Test Suptitle', fontsize=28)
  160. @image_comparison(['constrained_layout10.png'], style='mpl20',
  161. tol=0 if platform.machine() == 'x86_64' else 0.032)
  162. def test_constrained_layout10():
  163. """Test for handling legend outside axis"""
  164. fig, axs = plt.subplots(2, 2, layout="constrained")
  165. for ax in axs.flat:
  166. ax.plot(np.arange(12), label='This is a label')
  167. ax.legend(loc='center left', bbox_to_anchor=(0.8, 0.5))
  168. @image_comparison(['constrained_layout11.png'], style='mpl20')
  169. def test_constrained_layout11():
  170. """Test for multiple nested gridspecs"""
  171. fig = plt.figure(layout="constrained", figsize=(13, 3))
  172. gs0 = gridspec.GridSpec(1, 2, figure=fig)
  173. gsl = gridspec.GridSpecFromSubplotSpec(1, 2, gs0[0])
  174. gsl0 = gridspec.GridSpecFromSubplotSpec(2, 2, gsl[1])
  175. ax = fig.add_subplot(gs0[1])
  176. example_plot(ax, fontsize=9)
  177. axs = []
  178. for gs in gsl0:
  179. ax = fig.add_subplot(gs)
  180. axs += [ax]
  181. pcm = example_pcolor(ax, fontsize=9)
  182. fig.colorbar(pcm, ax=axs, shrink=0.6, aspect=70.)
  183. ax = fig.add_subplot(gsl[0])
  184. example_plot(ax, fontsize=9)
  185. @image_comparison(['constrained_layout11rat.png'], style='mpl20')
  186. def test_constrained_layout11rat():
  187. """Test for multiple nested gridspecs with width_ratios"""
  188. fig = plt.figure(layout="constrained", figsize=(10, 3))
  189. gs0 = gridspec.GridSpec(1, 2, figure=fig, width_ratios=[6, 1])
  190. gsl = gridspec.GridSpecFromSubplotSpec(1, 2, gs0[0])
  191. gsl0 = gridspec.GridSpecFromSubplotSpec(2, 2, gsl[1], height_ratios=[2, 1])
  192. ax = fig.add_subplot(gs0[1])
  193. example_plot(ax, fontsize=9)
  194. axs = []
  195. for gs in gsl0:
  196. ax = fig.add_subplot(gs)
  197. axs += [ax]
  198. pcm = example_pcolor(ax, fontsize=9)
  199. fig.colorbar(pcm, ax=axs, shrink=0.6, aspect=70.)
  200. ax = fig.add_subplot(gsl[0])
  201. example_plot(ax, fontsize=9)
  202. @image_comparison(['constrained_layout12.png'], style='mpl20')
  203. def test_constrained_layout12():
  204. """Test that very unbalanced labeling still works."""
  205. fig = plt.figure(layout="constrained", figsize=(6, 8))
  206. gs0 = gridspec.GridSpec(6, 2, figure=fig)
  207. ax1 = fig.add_subplot(gs0[:3, 1])
  208. ax2 = fig.add_subplot(gs0[3:, 1])
  209. example_plot(ax1, fontsize=18)
  210. example_plot(ax2, fontsize=18)
  211. ax = fig.add_subplot(gs0[0:2, 0])
  212. example_plot(ax, nodec=True)
  213. ax = fig.add_subplot(gs0[2:4, 0])
  214. example_plot(ax, nodec=True)
  215. ax = fig.add_subplot(gs0[4:, 0])
  216. example_plot(ax, nodec=True)
  217. ax.set_xlabel('x-label')
  218. @image_comparison(['constrained_layout13.png'], style='mpl20')
  219. def test_constrained_layout13():
  220. """Test that padding works."""
  221. fig, axs = plt.subplots(2, 2, layout="constrained")
  222. for ax in axs.flat:
  223. pcm = example_pcolor(ax, fontsize=12)
  224. fig.colorbar(pcm, ax=ax, shrink=0.6, aspect=20., pad=0.02)
  225. with pytest.raises(TypeError):
  226. fig.get_layout_engine().set(wpad=1, hpad=2)
  227. fig.get_layout_engine().set(w_pad=24./72., h_pad=24./72.)
  228. @image_comparison(['constrained_layout14.png'], style='mpl20')
  229. def test_constrained_layout14():
  230. """Test that padding works."""
  231. fig, axs = plt.subplots(2, 2, layout="constrained")
  232. for ax in axs.flat:
  233. pcm = example_pcolor(ax, fontsize=12)
  234. fig.colorbar(pcm, ax=ax, shrink=0.6, aspect=20., pad=0.02)
  235. fig.get_layout_engine().set(
  236. w_pad=3./72., h_pad=3./72.,
  237. hspace=0.2, wspace=0.2)
  238. @image_comparison(['constrained_layout15.png'], style='mpl20')
  239. def test_constrained_layout15():
  240. """Test that rcparams work."""
  241. mpl.rcParams['figure.constrained_layout.use'] = True
  242. fig, axs = plt.subplots(2, 2)
  243. for ax in axs.flat:
  244. example_plot(ax, fontsize=12)
  245. @image_comparison(['constrained_layout16.png'], style='mpl20')
  246. def test_constrained_layout16():
  247. """Test ax.set_position."""
  248. fig, ax = plt.subplots(layout="constrained")
  249. example_plot(ax, fontsize=12)
  250. ax2 = fig.add_axes([0.2, 0.2, 0.4, 0.4])
  251. @image_comparison(['constrained_layout17.png'], style='mpl20')
  252. def test_constrained_layout17():
  253. """Test uneven gridspecs"""
  254. fig = plt.figure(layout="constrained")
  255. gs = gridspec.GridSpec(3, 3, figure=fig)
  256. ax1 = fig.add_subplot(gs[0, 0])
  257. ax2 = fig.add_subplot(gs[0, 1:])
  258. ax3 = fig.add_subplot(gs[1:, 0:2])
  259. ax4 = fig.add_subplot(gs[1:, -1])
  260. example_plot(ax1)
  261. example_plot(ax2)
  262. example_plot(ax3)
  263. example_plot(ax4)
  264. def test_constrained_layout18():
  265. """Test twinx"""
  266. fig, ax = plt.subplots(layout="constrained")
  267. ax2 = ax.twinx()
  268. example_plot(ax)
  269. example_plot(ax2, fontsize=24)
  270. fig.draw_without_rendering()
  271. assert all(ax.get_position().extents == ax2.get_position().extents)
  272. def test_constrained_layout19():
  273. """Test twiny"""
  274. fig, ax = plt.subplots(layout="constrained")
  275. ax2 = ax.twiny()
  276. example_plot(ax)
  277. example_plot(ax2, fontsize=24)
  278. ax2.set_title('')
  279. ax.set_title('')
  280. fig.draw_without_rendering()
  281. assert all(ax.get_position().extents == ax2.get_position().extents)
  282. def test_constrained_layout20():
  283. """Smoke test cl does not mess up added Axes"""
  284. gx = np.linspace(-5, 5, 4)
  285. img = np.hypot(gx, gx[:, None])
  286. fig = plt.figure()
  287. ax = fig.add_axes([0, 0, 1, 1])
  288. mesh = ax.pcolormesh(gx, gx, img[:-1, :-1])
  289. fig.colorbar(mesh)
  290. def test_constrained_layout21():
  291. """#11035: repeated calls to suptitle should not alter the layout"""
  292. fig, ax = plt.subplots(layout="constrained")
  293. fig.suptitle("Suptitle0")
  294. fig.draw_without_rendering()
  295. extents0 = np.copy(ax.get_position().extents)
  296. fig.suptitle("Suptitle1")
  297. fig.draw_without_rendering()
  298. extents1 = np.copy(ax.get_position().extents)
  299. np.testing.assert_allclose(extents0, extents1)
  300. def test_constrained_layout22():
  301. """#11035: suptitle should not be include in CL if manually positioned"""
  302. fig, ax = plt.subplots(layout="constrained")
  303. fig.draw_without_rendering()
  304. extents0 = np.copy(ax.get_position().extents)
  305. fig.suptitle("Suptitle", y=0.5)
  306. fig.draw_without_rendering()
  307. extents1 = np.copy(ax.get_position().extents)
  308. np.testing.assert_allclose(extents0, extents1)
  309. def test_constrained_layout23():
  310. """
  311. Comment in #11035: suptitle used to cause an exception when
  312. reusing a figure w/ CL with ``clear=True``.
  313. """
  314. for i in range(2):
  315. fig = plt.figure(layout="constrained", clear=True, num="123")
  316. gs = fig.add_gridspec(1, 2)
  317. sub = gs[0].subgridspec(2, 2)
  318. fig.suptitle(f"Suptitle{i}")
  319. @image_comparison(['test_colorbar_location.png'],
  320. remove_text=True, style='mpl20')
  321. def test_colorbar_location():
  322. """
  323. Test that colorbar handling is as expected for various complicated
  324. cases...
  325. """
  326. # Remove this line when this test image is regenerated.
  327. plt.rcParams['pcolormesh.snap'] = False
  328. fig, axs = plt.subplots(4, 5, layout="constrained")
  329. for ax in axs.flat:
  330. pcm = example_pcolor(ax)
  331. ax.set_xlabel('')
  332. ax.set_ylabel('')
  333. fig.colorbar(pcm, ax=axs[:, 1], shrink=0.4)
  334. fig.colorbar(pcm, ax=axs[-1, :2], shrink=0.5, location='bottom')
  335. fig.colorbar(pcm, ax=axs[0, 2:], shrink=0.5, location='bottom', pad=0.05)
  336. fig.colorbar(pcm, ax=axs[-2, 3:], shrink=0.5, location='top')
  337. fig.colorbar(pcm, ax=axs[0, 0], shrink=0.5, location='left')
  338. fig.colorbar(pcm, ax=axs[1:3, 2], shrink=0.5, location='right')
  339. def test_hidden_axes():
  340. # test that if we make an Axes not visible that constrained_layout
  341. # still works. Note the axes still takes space in the layout
  342. # (as does a gridspec slot that is empty)
  343. fig, axs = plt.subplots(2, 2, layout="constrained")
  344. axs[0, 1].set_visible(False)
  345. fig.draw_without_rendering()
  346. extents1 = np.copy(axs[0, 0].get_position().extents)
  347. np.testing.assert_allclose(
  348. extents1, [0.046918, 0.541204, 0.477409, 0.980555], rtol=1e-5)
  349. def test_colorbar_align():
  350. for location in ['right', 'left', 'top', 'bottom']:
  351. fig, axs = plt.subplots(2, 2, layout="constrained")
  352. cbs = []
  353. for nn, ax in enumerate(axs.flat):
  354. ax.tick_params(direction='in')
  355. pc = example_pcolor(ax)
  356. cb = fig.colorbar(pc, ax=ax, location=location, shrink=0.6,
  357. pad=0.04)
  358. cbs += [cb]
  359. cb.ax.tick_params(direction='in')
  360. if nn != 1:
  361. cb.ax.xaxis.set_ticks([])
  362. cb.ax.yaxis.set_ticks([])
  363. ax.set_xticklabels([])
  364. ax.set_yticklabels([])
  365. fig.get_layout_engine().set(w_pad=4 / 72, h_pad=4 / 72,
  366. hspace=0.1, wspace=0.1)
  367. fig.draw_without_rendering()
  368. if location in ['left', 'right']:
  369. np.testing.assert_allclose(cbs[0].ax.get_position().x0,
  370. cbs[2].ax.get_position().x0)
  371. np.testing.assert_allclose(cbs[1].ax.get_position().x0,
  372. cbs[3].ax.get_position().x0)
  373. else:
  374. np.testing.assert_allclose(cbs[0].ax.get_position().y0,
  375. cbs[1].ax.get_position().y0)
  376. np.testing.assert_allclose(cbs[2].ax.get_position().y0,
  377. cbs[3].ax.get_position().y0)
  378. @image_comparison(['test_colorbars_no_overlapV.png'], style='mpl20')
  379. def test_colorbars_no_overlapV():
  380. fig = plt.figure(figsize=(2, 4), layout="constrained")
  381. axs = fig.subplots(2, 1, sharex=True, sharey=True)
  382. for ax in axs:
  383. ax.yaxis.set_major_formatter(ticker.NullFormatter())
  384. ax.tick_params(axis='both', direction='in')
  385. im = ax.imshow([[1, 2], [3, 4]])
  386. fig.colorbar(im, ax=ax, orientation="vertical")
  387. fig.suptitle("foo")
  388. @image_comparison(['test_colorbars_no_overlapH.png'], style='mpl20')
  389. def test_colorbars_no_overlapH():
  390. fig = plt.figure(figsize=(4, 2), layout="constrained")
  391. fig.suptitle("foo")
  392. axs = fig.subplots(1, 2, sharex=True, sharey=True)
  393. for ax in axs:
  394. ax.yaxis.set_major_formatter(ticker.NullFormatter())
  395. ax.tick_params(axis='both', direction='in')
  396. im = ax.imshow([[1, 2], [3, 4]])
  397. fig.colorbar(im, ax=ax, orientation="horizontal")
  398. def test_manually_set_position():
  399. fig, axs = plt.subplots(1, 2, layout="constrained")
  400. axs[0].set_position([0.2, 0.2, 0.3, 0.3])
  401. fig.draw_without_rendering()
  402. pp = axs[0].get_position()
  403. np.testing.assert_allclose(pp, [[0.2, 0.2], [0.5, 0.5]])
  404. fig, axs = plt.subplots(1, 2, layout="constrained")
  405. axs[0].set_position([0.2, 0.2, 0.3, 0.3])
  406. pc = axs[0].pcolormesh(np.random.rand(20, 20))
  407. fig.colorbar(pc, ax=axs[0])
  408. fig.draw_without_rendering()
  409. pp = axs[0].get_position()
  410. np.testing.assert_allclose(pp, [[0.2, 0.2], [0.44, 0.5]])
  411. @image_comparison(['test_bboxtight.png'],
  412. remove_text=True, style='mpl20',
  413. savefig_kwarg={'bbox_inches': 'tight'})
  414. def test_bboxtight():
  415. fig, ax = plt.subplots(layout="constrained")
  416. ax.set_aspect(1.)
  417. @image_comparison(['test_bbox.png'],
  418. remove_text=True, style='mpl20',
  419. savefig_kwarg={'bbox_inches':
  420. mtransforms.Bbox([[0.5, 0], [2.5, 2]])})
  421. def test_bbox():
  422. fig, ax = plt.subplots(layout="constrained")
  423. ax.set_aspect(1.)
  424. def test_align_labels():
  425. """
  426. Tests for a bug in which constrained layout and align_ylabels on
  427. three unevenly sized subplots, one of whose y tick labels include
  428. negative numbers, drives the non-negative subplots' y labels off
  429. the edge of the plot
  430. """
  431. fig, (ax3, ax1, ax2) = plt.subplots(3, 1, layout="constrained",
  432. figsize=(6.4, 8),
  433. gridspec_kw={"height_ratios": (1, 1,
  434. 0.7)})
  435. ax1.set_ylim(0, 1)
  436. ax1.set_ylabel("Label")
  437. ax2.set_ylim(-1.5, 1.5)
  438. ax2.set_ylabel("Label")
  439. ax3.set_ylim(0, 1)
  440. ax3.set_ylabel("Label")
  441. fig.align_ylabels(axs=(ax3, ax1, ax2))
  442. fig.draw_without_rendering()
  443. after_align = [ax1.yaxis.label.get_window_extent(),
  444. ax2.yaxis.label.get_window_extent(),
  445. ax3.yaxis.label.get_window_extent()]
  446. # ensure labels are approximately aligned
  447. np.testing.assert_allclose([after_align[0].x0, after_align[2].x0],
  448. after_align[1].x0, rtol=0, atol=1e-05)
  449. # ensure labels do not go off the edge
  450. assert after_align[0].x0 >= 1
  451. def test_suplabels():
  452. fig, ax = plt.subplots(layout="constrained")
  453. fig.draw_without_rendering()
  454. pos0 = ax.get_tightbbox(fig.canvas.get_renderer())
  455. fig.supxlabel('Boo')
  456. fig.supylabel('Booy')
  457. fig.draw_without_rendering()
  458. pos = ax.get_tightbbox(fig.canvas.get_renderer())
  459. assert pos.y0 > pos0.y0 + 10.0
  460. assert pos.x0 > pos0.x0 + 10.0
  461. fig, ax = plt.subplots(layout="constrained")
  462. fig.draw_without_rendering()
  463. pos0 = ax.get_tightbbox(fig.canvas.get_renderer())
  464. # check that specifying x (y) doesn't ruin the layout
  465. fig.supxlabel('Boo', x=0.5)
  466. fig.supylabel('Boo', y=0.5)
  467. fig.draw_without_rendering()
  468. pos = ax.get_tightbbox(fig.canvas.get_renderer())
  469. assert pos.y0 > pos0.y0 + 10.0
  470. assert pos.x0 > pos0.x0 + 10.0
  471. def test_gridspec_addressing():
  472. fig = plt.figure()
  473. gs = fig.add_gridspec(3, 3)
  474. sp = fig.add_subplot(gs[0:, 1:])
  475. fig.draw_without_rendering()
  476. def test_discouraged_api():
  477. fig, ax = plt.subplots(constrained_layout=True)
  478. fig.draw_without_rendering()
  479. with pytest.warns(PendingDeprecationWarning,
  480. match="will be deprecated"):
  481. fig, ax = plt.subplots()
  482. fig.set_constrained_layout(True)
  483. fig.draw_without_rendering()
  484. with pytest.warns(PendingDeprecationWarning,
  485. match="will be deprecated"):
  486. fig, ax = plt.subplots()
  487. fig.set_constrained_layout({'w_pad': 0.02, 'h_pad': 0.02})
  488. fig.draw_without_rendering()
  489. def test_kwargs():
  490. fig, ax = plt.subplots(constrained_layout={'h_pad': 0.02})
  491. fig.draw_without_rendering()
  492. def test_rect():
  493. fig, ax = plt.subplots(layout='constrained')
  494. fig.get_layout_engine().set(rect=[0, 0, 0.5, 0.5])
  495. fig.draw_without_rendering()
  496. ppos = ax.get_position()
  497. assert ppos.x1 < 0.5
  498. assert ppos.y1 < 0.5
  499. fig, ax = plt.subplots(layout='constrained')
  500. fig.get_layout_engine().set(rect=[0.2, 0.2, 0.3, 0.3])
  501. fig.draw_without_rendering()
  502. ppos = ax.get_position()
  503. assert ppos.x1 < 0.5
  504. assert ppos.y1 < 0.5
  505. assert ppos.x0 > 0.2
  506. assert ppos.y0 > 0.2
  507. def test_compressed1():
  508. fig, axs = plt.subplots(3, 2, layout='compressed',
  509. sharex=True, sharey=True)
  510. for ax in axs.flat:
  511. pc = ax.imshow(np.random.randn(20, 20))
  512. fig.colorbar(pc, ax=axs)
  513. fig.draw_without_rendering()
  514. pos = axs[0, 0].get_position()
  515. np.testing.assert_allclose(pos.x0, 0.2381, atol=1e-2)
  516. pos = axs[0, 1].get_position()
  517. np.testing.assert_allclose(pos.x1, 0.7024, atol=1e-3)
  518. # wider than tall
  519. fig, axs = plt.subplots(2, 3, layout='compressed',
  520. sharex=True, sharey=True, figsize=(5, 4))
  521. for ax in axs.flat:
  522. pc = ax.imshow(np.random.randn(20, 20))
  523. fig.colorbar(pc, ax=axs)
  524. fig.draw_without_rendering()
  525. pos = axs[0, 0].get_position()
  526. np.testing.assert_allclose(pos.x0, 0.05653, atol=1e-3)
  527. np.testing.assert_allclose(pos.y1, 0.8603, atol=1e-2)
  528. pos = axs[1, 2].get_position()
  529. np.testing.assert_allclose(pos.x1, 0.8728, atol=1e-3)
  530. np.testing.assert_allclose(pos.y0, 0.1808, atol=1e-2)
  531. def test_compressed_suptitle():
  532. fig, (ax0, ax1) = plt.subplots(
  533. nrows=2, figsize=(4, 10), layout="compressed",
  534. gridspec_kw={"height_ratios": (1 / 4, 3 / 4), "hspace": 0})
  535. ax0.axis("equal")
  536. ax0.set_box_aspect(1/3)
  537. ax1.axis("equal")
  538. ax1.set_box_aspect(1)
  539. title = fig.suptitle("Title")
  540. fig.draw_without_rendering()
  541. assert title.get_position()[1] == pytest.approx(0.7491, abs=1e-3)
  542. title = fig.suptitle("Title", y=0.98)
  543. fig.draw_without_rendering()
  544. assert title.get_position()[1] == 0.98
  545. title = fig.suptitle("Title", in_layout=False)
  546. fig.draw_without_rendering()
  547. assert title.get_position()[1] == 0.98
  548. @pytest.mark.parametrize('arg, state', [
  549. (True, True),
  550. (False, False),
  551. ({}, True),
  552. ({'rect': None}, True)
  553. ])
  554. def test_set_constrained_layout(arg, state):
  555. fig, ax = plt.subplots(constrained_layout=arg)
  556. assert fig.get_constrained_layout() is state
  557. def test_constrained_toggle():
  558. fig, ax = plt.subplots()
  559. with pytest.warns(PendingDeprecationWarning):
  560. fig.set_constrained_layout(True)
  561. assert fig.get_constrained_layout()
  562. fig.set_constrained_layout(False)
  563. assert not fig.get_constrained_layout()
  564. fig.set_constrained_layout(True)
  565. assert fig.get_constrained_layout()
  566. def test_layout_leak():
  567. # Make sure there aren't any cyclic references when using LayoutGrid
  568. # GH #25853
  569. fig = plt.figure(constrained_layout=True, figsize=(10, 10))
  570. fig.add_subplot()
  571. fig.draw_without_rendering()
  572. plt.close("all")
  573. del fig
  574. gc.collect()
  575. assert not any(isinstance(obj, mpl._layoutgrid.LayoutGrid)
  576. for obj in gc.get_objects())
  577. def test_submerged_subfig():
  578. """
  579. Test that the submerged margin logic does not get called multiple times
  580. on same axes if it is already in a subfigure
  581. """
  582. fig = plt.figure(figsize=(4, 5), layout='constrained')
  583. figures = fig.subfigures(3, 1)
  584. axs = []
  585. for f in figures.flatten():
  586. gs = f.add_gridspec(2, 2)
  587. for i in range(2):
  588. axs += [f.add_subplot(gs[i, 0])]
  589. axs[-1].plot()
  590. f.add_subplot(gs[:, 1]).plot()
  591. fig.draw_without_rendering()
  592. for ax in axs[1:]:
  593. assert np.allclose(ax.get_position().bounds[-1],
  594. axs[0].get_position().bounds[-1], atol=1e-6)