test_constrainedlayout.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402
  1. import numpy as np
  2. import pytest
  3. from matplotlib.testing.decorators import image_comparison
  4. import matplotlib.pyplot as plt
  5. import matplotlib.gridspec as gridspec
  6. from matplotlib import ticker, rcParams
  7. def example_plot(ax, fontsize=12, nodec=False):
  8. ax.plot([1, 2])
  9. ax.locator_params(nbins=3)
  10. if not nodec:
  11. ax.set_xlabel('x-label', fontsize=fontsize)
  12. ax.set_ylabel('y-label', fontsize=fontsize)
  13. ax.set_title('Title', fontsize=fontsize)
  14. else:
  15. ax.set_xticklabels('')
  16. ax.set_yticklabels('')
  17. def example_pcolor(ax, fontsize=12):
  18. dx, dy = 0.6, 0.6
  19. y, x = np.mgrid[slice(-3, 3 + dy, dy),
  20. slice(-3, 3 + dx, dx)]
  21. z = (1 - x / 2. + x ** 5 + y ** 3) * np.exp(-x ** 2 - y ** 2)
  22. pcm = ax.pcolormesh(x, y, z, cmap='RdBu_r', vmin=-1., vmax=1.,
  23. rasterized=True)
  24. # ax.locator_params(nbins=3)
  25. ax.set_xlabel('x-label', fontsize=fontsize)
  26. ax.set_ylabel('y-label', fontsize=fontsize)
  27. ax.set_title('Title', fontsize=fontsize)
  28. return pcm
  29. @image_comparison(['constrained_layout1.png'])
  30. def test_constrained_layout1():
  31. 'Test constrained_layout for a single subplot'
  32. fig = plt.figure(constrained_layout=True)
  33. ax = fig.add_subplot(111)
  34. example_plot(ax, fontsize=24)
  35. @image_comparison(['constrained_layout2.png'])
  36. def test_constrained_layout2():
  37. 'Test constrained_layout for 2x2 subplots'
  38. fig, axs = plt.subplots(2, 2, constrained_layout=True)
  39. for ax in axs.flat:
  40. example_plot(ax, fontsize=24)
  41. @image_comparison(['constrained_layout3.png'])
  42. def test_constrained_layout3():
  43. 'Test constrained_layout for colorbars with subplots'
  44. fig, axs = plt.subplots(2, 2, constrained_layout=True)
  45. for nn, ax in enumerate(axs.flat):
  46. pcm = example_pcolor(ax, fontsize=24)
  47. if nn == 3:
  48. pad = 0.08
  49. else:
  50. pad = 0.02 # default
  51. fig.colorbar(pcm, ax=ax, pad=pad)
  52. @image_comparison(['constrained_layout4'])
  53. def test_constrained_layout4():
  54. 'Test constrained_layout for a single colorbar with subplots'
  55. fig, axs = plt.subplots(2, 2, constrained_layout=True)
  56. for ax in axs.flat:
  57. pcm = example_pcolor(ax, fontsize=24)
  58. fig.colorbar(pcm, ax=axs, pad=0.01, shrink=0.6)
  59. @image_comparison(['constrained_layout5.png'], tol=5.e-2)
  60. def test_constrained_layout5():
  61. '''
  62. Test constrained_layout for a single colorbar with subplots,
  63. colorbar bottom
  64. '''
  65. fig, axs = plt.subplots(2, 2, constrained_layout=True)
  66. for ax in axs.flat:
  67. pcm = example_pcolor(ax, fontsize=24)
  68. fig.colorbar(pcm, ax=axs,
  69. use_gridspec=False, pad=0.01, shrink=0.6,
  70. location='bottom')
  71. @image_comparison(['constrained_layout6.png'])
  72. def test_constrained_layout6():
  73. 'Test constrained_layout for nested gridspecs'
  74. fig = plt.figure(constrained_layout=True)
  75. gs = fig.add_gridspec(1, 2, figure=fig)
  76. gsl = gs[0].subgridspec(2, 2)
  77. gsr = gs[1].subgridspec(1, 2)
  78. axsl = []
  79. for gs in gsl:
  80. ax = fig.add_subplot(gs)
  81. axsl += [ax]
  82. example_plot(ax, fontsize=12)
  83. ax.set_xlabel('x-label\nMultiLine')
  84. axsr = []
  85. for gs in gsr:
  86. ax = fig.add_subplot(gs)
  87. axsr += [ax]
  88. pcm = example_pcolor(ax, fontsize=12)
  89. fig.colorbar(pcm, ax=axsr,
  90. pad=0.01, shrink=0.99, location='bottom',
  91. ticks=ticker.MaxNLocator(nbins=5))
  92. def test_constrained_layout7():
  93. 'Test for proper warning if fig not set in GridSpec'
  94. with pytest.warns(UserWarning, match='Calling figure.constrained_layout, '
  95. 'but figure not setup to do constrained layout'):
  96. fig = plt.figure(constrained_layout=True)
  97. gs = gridspec.GridSpec(1, 2)
  98. gsl = gridspec.GridSpecFromSubplotSpec(2, 2, gs[0])
  99. gsr = gridspec.GridSpecFromSubplotSpec(1, 2, gs[1])
  100. for gs in gsl:
  101. fig.add_subplot(gs)
  102. # need to trigger a draw to get warning
  103. fig.draw(fig.canvas.get_renderer())
  104. @image_comparison(['constrained_layout8.png'])
  105. def test_constrained_layout8():
  106. 'Test for gridspecs that are not completely full'
  107. fig = plt.figure(figsize=(10, 5), constrained_layout=True)
  108. gs = gridspec.GridSpec(3, 5, figure=fig)
  109. axs = []
  110. for j in [0, 1]:
  111. if j == 0:
  112. ilist = [1]
  113. else:
  114. ilist = [0, 4]
  115. for i in ilist:
  116. ax = fig.add_subplot(gs[j, i])
  117. axs += [ax]
  118. pcm = example_pcolor(ax, fontsize=9)
  119. if i > 0:
  120. ax.set_ylabel('')
  121. if j < 1:
  122. ax.set_xlabel('')
  123. ax.set_title('')
  124. ax = fig.add_subplot(gs[2, :])
  125. axs += [ax]
  126. pcm = example_pcolor(ax, fontsize=9)
  127. fig.colorbar(pcm, ax=axs, pad=0.01, shrink=0.6)
  128. @image_comparison(['constrained_layout9.png'])
  129. def test_constrained_layout9():
  130. 'Test for handling suptitle and for sharex and sharey'
  131. fig, axs = plt.subplots(2, 2, constrained_layout=True,
  132. sharex=False, sharey=False)
  133. for ax in axs.flat:
  134. pcm = example_pcolor(ax, fontsize=24)
  135. ax.set_xlabel('')
  136. ax.set_ylabel('')
  137. ax.set_aspect(2.)
  138. fig.colorbar(pcm, ax=axs, pad=0.01, shrink=0.6)
  139. fig.suptitle('Test Suptitle', fontsize=28)
  140. @image_comparison(['constrained_layout10.png'])
  141. def test_constrained_layout10():
  142. 'Test for handling legend outside axis'
  143. fig, axs = plt.subplots(2, 2, constrained_layout=True)
  144. for ax in axs.flat:
  145. ax.plot(np.arange(12), label='This is a label')
  146. ax.legend(loc='center left', bbox_to_anchor=(0.8, 0.5))
  147. @image_comparison(['constrained_layout11.png'])
  148. def test_constrained_layout11():
  149. 'Test for multiple nested gridspecs'
  150. fig = plt.figure(constrained_layout=True, figsize=(10, 3))
  151. gs0 = gridspec.GridSpec(1, 2, figure=fig)
  152. gsl = gridspec.GridSpecFromSubplotSpec(1, 2, gs0[0])
  153. gsl0 = gridspec.GridSpecFromSubplotSpec(2, 2, gsl[1])
  154. ax = fig.add_subplot(gs0[1])
  155. example_plot(ax, fontsize=9)
  156. axs = []
  157. for gs in gsl0:
  158. ax = fig.add_subplot(gs)
  159. axs += [ax]
  160. pcm = example_pcolor(ax, fontsize=9)
  161. fig.colorbar(pcm, ax=axs, shrink=0.6, aspect=70.)
  162. ax = fig.add_subplot(gsl[0])
  163. example_plot(ax, fontsize=9)
  164. @image_comparison(['constrained_layout11rat.png'])
  165. def test_constrained_layout11rat():
  166. 'Test for multiple nested gridspecs with width_ratios'
  167. fig = plt.figure(constrained_layout=True, figsize=(10, 3))
  168. gs0 = gridspec.GridSpec(1, 2, figure=fig, width_ratios=[6., 1.])
  169. gsl = gridspec.GridSpecFromSubplotSpec(1, 2, gs0[0])
  170. gsl0 = gridspec.GridSpecFromSubplotSpec(2, 2, gsl[1],
  171. height_ratios=[2., 1.])
  172. ax = fig.add_subplot(gs0[1])
  173. example_plot(ax, fontsize=9)
  174. axs = []
  175. for gs in gsl0:
  176. ax = fig.add_subplot(gs)
  177. axs += [ax]
  178. pcm = example_pcolor(ax, fontsize=9)
  179. fig.colorbar(pcm, ax=axs, shrink=0.6, aspect=70.)
  180. ax = fig.add_subplot(gsl[0])
  181. example_plot(ax, fontsize=9)
  182. @image_comparison(['constrained_layout12.png'])
  183. def test_constrained_layout12():
  184. 'Test that very unbalanced labeling still works.'
  185. fig = plt.figure(constrained_layout=True)
  186. gs0 = gridspec.GridSpec(6, 2, figure=fig)
  187. ax1 = fig.add_subplot(gs0[:3, 1])
  188. ax2 = fig.add_subplot(gs0[3:, 1])
  189. example_plot(ax1, fontsize=24)
  190. example_plot(ax2, fontsize=24)
  191. ax = fig.add_subplot(gs0[0:2, 0])
  192. example_plot(ax, nodec=True)
  193. ax = fig.add_subplot(gs0[2:4, 0])
  194. example_plot(ax, nodec=True)
  195. ax = fig.add_subplot(gs0[4:, 0])
  196. example_plot(ax, nodec=True)
  197. ax.set_xlabel('x-label')
  198. @image_comparison(['constrained_layout13.png'], tol=2.e-2)
  199. def test_constrained_layout13():
  200. 'Test that padding works.'
  201. fig, axs = plt.subplots(2, 2, constrained_layout=True)
  202. for ax in axs.flat:
  203. pcm = example_pcolor(ax, fontsize=12)
  204. fig.colorbar(pcm, ax=ax, shrink=0.6, aspect=20., pad=0.02)
  205. fig.set_constrained_layout_pads(w_pad=24./72., h_pad=24./72.)
  206. @image_comparison(['constrained_layout14.png'])
  207. def test_constrained_layout14():
  208. 'Test that padding works.'
  209. fig, axs = plt.subplots(2, 2, constrained_layout=True)
  210. for ax in axs.flat:
  211. pcm = example_pcolor(ax, fontsize=12)
  212. fig.colorbar(pcm, ax=ax, shrink=0.6, aspect=20., pad=0.02)
  213. fig.set_constrained_layout_pads(
  214. w_pad=3./72., h_pad=3./72.,
  215. hspace=0.2, wspace=0.2)
  216. @image_comparison(['constrained_layout15.png'])
  217. def test_constrained_layout15():
  218. 'Test that rcparams work.'
  219. rcParams['figure.constrained_layout.use'] = True
  220. fig, axs = plt.subplots(2, 2)
  221. for ax in axs.flat:
  222. example_plot(ax, fontsize=12)
  223. @image_comparison(['constrained_layout16.png'])
  224. def test_constrained_layout16():
  225. 'Test ax.set_position.'
  226. fig, ax = plt.subplots(constrained_layout=True)
  227. example_plot(ax, fontsize=12)
  228. ax2 = fig.add_axes([0.2, 0.2, 0.4, 0.4])
  229. @image_comparison(['constrained_layout17.png'])
  230. def test_constrained_layout17():
  231. 'Test uneven gridspecs'
  232. fig = plt.figure(constrained_layout=True)
  233. gs = gridspec.GridSpec(3, 3, figure=fig)
  234. ax1 = fig.add_subplot(gs[0, 0])
  235. ax2 = fig.add_subplot(gs[0, 1:])
  236. ax3 = fig.add_subplot(gs[1:, 0:2])
  237. ax4 = fig.add_subplot(gs[1:, -1])
  238. example_plot(ax1)
  239. example_plot(ax2)
  240. example_plot(ax3)
  241. example_plot(ax4)
  242. def test_constrained_layout18():
  243. 'Test twinx'
  244. fig, ax = plt.subplots(constrained_layout=True)
  245. ax2 = ax.twinx()
  246. example_plot(ax)
  247. example_plot(ax2, fontsize=24)
  248. fig.canvas.draw()
  249. assert all(ax.get_position().extents == ax2.get_position().extents)
  250. def test_constrained_layout19():
  251. 'Test twiny'
  252. fig, ax = plt.subplots(constrained_layout=True)
  253. ax2 = ax.twiny()
  254. example_plot(ax)
  255. example_plot(ax2, fontsize=24)
  256. ax2.set_title('')
  257. ax.set_title('')
  258. fig.canvas.draw()
  259. assert all(ax.get_position().extents == ax2.get_position().extents)
  260. def test_constrained_layout20():
  261. 'Smoke test cl does not mess up added axes'
  262. gx = np.linspace(-5, 5, 4)
  263. img = np.hypot(gx, gx[:, None])
  264. fig = plt.figure()
  265. ax = fig.add_axes([0, 0, 1, 1])
  266. mesh = ax.pcolormesh(gx, gx, img)
  267. fig.colorbar(mesh)
  268. def test_constrained_layout21():
  269. '#11035: repeated calls to suptitle should not alter the layout'
  270. fig, ax = plt.subplots(constrained_layout=True)
  271. fig.suptitle("Suptitle0")
  272. fig.canvas.draw()
  273. extents0 = np.copy(ax.get_position().extents)
  274. fig.suptitle("Suptitle1")
  275. fig.canvas.draw()
  276. extents1 = np.copy(ax.get_position().extents)
  277. np.testing.assert_allclose(extents0, extents1)
  278. def test_constrained_layout22():
  279. '#11035: suptitle should not be include in CL if manually positioned'
  280. fig, ax = plt.subplots(constrained_layout=True)
  281. fig.canvas.draw()
  282. extents0 = np.copy(ax.get_position().extents)
  283. fig.suptitle("Suptitle", y=0.5)
  284. fig.canvas.draw()
  285. extents1 = np.copy(ax.get_position().extents)
  286. np.testing.assert_allclose(extents0, extents1)
  287. def test_constrained_layout23():
  288. '''
  289. Comment in #11035: suptitle used to cause an exception when
  290. reusing a figure w/ CL with ``clear=True``.
  291. '''
  292. for i in range(2):
  293. fig, ax = plt.subplots(num="123", constrained_layout=True, clear=True)
  294. fig.suptitle("Suptitle{}".format(i))
  295. # This test occasionally fails the image comparison tests, so we mark as
  296. # flaky. Apparently the constraint solver occasionally doesn't fully
  297. # optimize. Would be nice if this were more deterministic...
  298. @pytest.mark.timeout(30)
  299. @pytest.mark.flaky(reruns=3)
  300. @image_comparison(['test_colorbar_location.png'],
  301. remove_text=True, style='mpl20')
  302. def test_colorbar_location():
  303. """
  304. Test that colorbar handling is as expected for various complicated
  305. cases...
  306. """
  307. fig, axs = plt.subplots(4, 5, constrained_layout=True)
  308. for ax in axs.flat:
  309. pcm = example_pcolor(ax)
  310. ax.set_xlabel('')
  311. ax.set_ylabel('')
  312. fig.colorbar(pcm, ax=axs[:, 1], shrink=0.4)
  313. fig.colorbar(pcm, ax=axs[-1, :2], shrink=0.5, location='bottom')
  314. fig.colorbar(pcm, ax=axs[0, 2:], shrink=0.5, location='bottom')
  315. fig.colorbar(pcm, ax=axs[-2, 3:], shrink=0.5, location='top')
  316. fig.colorbar(pcm, ax=axs[0, 0], shrink=0.5, location='left')
  317. fig.colorbar(pcm, ax=axs[1:3, 2], shrink=0.5, location='right')
  318. def test_hidden_axes():
  319. # test that if we make an axes not visible that constrained_layout
  320. # still works. Note the axes still takes space in the layout
  321. # (as does a gridspec slot that is empty)
  322. fig, axs = plt.subplots(2, 2, constrained_layout=True)
  323. axs[0, 1].set_visible(False)
  324. fig.canvas.draw()
  325. extents1 = np.copy(axs[0, 0].get_position().extents)
  326. np.testing.assert_allclose(extents1,
  327. [0.045552, 0.548288, 0.47319, 0.982638], rtol=1e-5)