test_contour.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398
  1. import datetime
  2. import platform
  3. import re
  4. import numpy as np
  5. from numpy.testing import assert_array_almost_equal
  6. import matplotlib as mpl
  7. from matplotlib.testing.decorators import image_comparison
  8. from matplotlib import pyplot as plt, rc_context
  9. from matplotlib.colors import LogNorm
  10. import pytest
  11. def test_contour_shape_1d_valid():
  12. x = np.arange(10)
  13. y = np.arange(9)
  14. z = np.random.random((9, 10))
  15. fig, ax = plt.subplots()
  16. ax.contour(x, y, z)
  17. def test_contour_shape_2d_valid():
  18. x = np.arange(10)
  19. y = np.arange(9)
  20. xg, yg = np.meshgrid(x, y)
  21. z = np.random.random((9, 10))
  22. fig, ax = plt.subplots()
  23. ax.contour(xg, yg, z)
  24. @pytest.mark.parametrize("args, message", [
  25. ((np.arange(9), np.arange(9), np.empty((9, 10))),
  26. 'Length of x (9) must match number of columns in z (10)'),
  27. ((np.arange(10), np.arange(10), np.empty((9, 10))),
  28. 'Length of y (10) must match number of rows in z (9)'),
  29. ((np.empty((10, 10)), np.arange(10), np.empty((9, 10))),
  30. 'Number of dimensions of x (2) and y (1) do not match'),
  31. ((np.arange(10), np.empty((10, 10)), np.empty((9, 10))),
  32. 'Number of dimensions of x (1) and y (2) do not match'),
  33. ((np.empty((9, 9)), np.empty((9, 10)), np.empty((9, 10))),
  34. 'Shapes of x (9, 9) and z (9, 10) do not match'),
  35. ((np.empty((9, 10)), np.empty((9, 9)), np.empty((9, 10))),
  36. 'Shapes of y (9, 9) and z (9, 10) do not match'),
  37. ((np.empty((3, 3, 3)), np.empty((3, 3, 3)), np.empty((9, 10))),
  38. 'Inputs x and y must be 1D or 2D, not 3D'),
  39. ((np.empty((3, 3, 3)), np.empty((3, 3, 3)), np.empty((3, 3, 3))),
  40. 'Input z must be 2D, not 3D'),
  41. (([[0]],), # github issue 8197
  42. 'Input z must be at least a (2, 2) shaped array, but has shape (1, 1)'),
  43. (([0], [0], [[0]]),
  44. 'Input z must be at least a (2, 2) shaped array, but has shape (1, 1)'),
  45. ])
  46. def test_contour_shape_error(args, message):
  47. fig, ax = plt.subplots()
  48. with pytest.raises(TypeError, match=re.escape(message)):
  49. ax.contour(*args)
  50. def test_contour_empty_levels():
  51. x = np.arange(9)
  52. z = np.random.random((9, 9))
  53. fig, ax = plt.subplots()
  54. with pytest.warns(UserWarning) as record:
  55. ax.contour(x, x, z, levels=[])
  56. assert len(record) == 1
  57. def test_contour_Nlevels():
  58. # A scalar levels arg or kwarg should trigger auto level generation.
  59. # https://github.com/matplotlib/matplotlib/issues/11913
  60. z = np.arange(12).reshape((3, 4))
  61. fig, ax = plt.subplots()
  62. cs1 = ax.contour(z, 5)
  63. assert len(cs1.levels) > 1
  64. cs2 = ax.contour(z, levels=5)
  65. assert (cs1.levels == cs2.levels).all()
  66. def test_contour_badlevel_fmt():
  67. # Test edge case from https://github.com/matplotlib/matplotlib/issues/9742
  68. # User supplied fmt for each level as a dictionary, but Matplotlib changed
  69. # the level to the minimum data value because no contours possible.
  70. # This was fixed in https://github.com/matplotlib/matplotlib/pull/9743
  71. x = np.arange(9)
  72. z = np.zeros((9, 9))
  73. fig, ax = plt.subplots()
  74. fmt = {1.: '%1.2f'}
  75. with pytest.warns(UserWarning) as record:
  76. cs = ax.contour(x, x, z, levels=[1.])
  77. ax.clabel(cs, fmt=fmt)
  78. assert len(record) == 1
  79. def test_contour_uniform_z():
  80. x = np.arange(9)
  81. z = np.ones((9, 9))
  82. fig, ax = plt.subplots()
  83. with pytest.warns(UserWarning) as record:
  84. ax.contour(x, x, z)
  85. assert len(record) == 1
  86. @image_comparison(['contour_manual_labels'],
  87. savefig_kwarg={'dpi': 200}, remove_text=True, style='mpl20')
  88. def test_contour_manual_labels():
  89. x, y = np.meshgrid(np.arange(0, 10), np.arange(0, 10))
  90. z = np.max(np.dstack([abs(x), abs(y)]), 2)
  91. plt.figure(figsize=(6, 2), dpi=200)
  92. cs = plt.contour(x, y, z)
  93. pts = np.array([(1.5, 3.0), (1.5, 4.4), (1.5, 6.0)])
  94. plt.clabel(cs, manual=pts)
  95. @image_comparison(['contour_labels_size_color.png'],
  96. remove_text=True, style='mpl20')
  97. def test_contour_labels_size_color():
  98. x, y = np.meshgrid(np.arange(0, 10), np.arange(0, 10))
  99. z = np.max(np.dstack([abs(x), abs(y)]), 2)
  100. plt.figure(figsize=(6, 2))
  101. cs = plt.contour(x, y, z)
  102. pts = np.array([(1.5, 3.0), (1.5, 4.4), (1.5, 6.0)])
  103. plt.clabel(cs, manual=pts, fontsize='small', colors=('r', 'g'))
  104. @image_comparison(['contour_manual_colors_and_levels.png'], remove_text=True)
  105. def test_given_colors_levels_and_extends():
  106. _, axs = plt.subplots(2, 4)
  107. data = np.arange(12).reshape(3, 4)
  108. colors = ['red', 'yellow', 'pink', 'blue', 'black']
  109. levels = [2, 4, 8, 10]
  110. for i, ax in enumerate(axs.flat):
  111. filled = i % 2 == 0.
  112. extend = ['neither', 'min', 'max', 'both'][i // 2]
  113. if filled:
  114. # If filled, we have 3 colors with no extension,
  115. # 4 colors with one extension, and 5 colors with both extensions
  116. first_color = 1 if extend in ['max', 'neither'] else None
  117. last_color = -1 if extend in ['min', 'neither'] else None
  118. c = ax.contourf(data, colors=colors[first_color:last_color],
  119. levels=levels, extend=extend)
  120. else:
  121. # If not filled, we have 4 levels and 4 colors
  122. c = ax.contour(data, colors=colors[:-1],
  123. levels=levels, extend=extend)
  124. plt.colorbar(c, ax=ax)
  125. @image_comparison(['contour_datetime_axis.png'],
  126. remove_text=False, style='mpl20')
  127. def test_contour_datetime_axis():
  128. fig = plt.figure()
  129. fig.subplots_adjust(hspace=0.4, top=0.98, bottom=.15)
  130. base = datetime.datetime(2013, 1, 1)
  131. x = np.array([base + datetime.timedelta(days=d) for d in range(20)])
  132. y = np.arange(20)
  133. z1, z2 = np.meshgrid(np.arange(20), np.arange(20))
  134. z = z1 * z2
  135. plt.subplot(221)
  136. plt.contour(x, y, z)
  137. plt.subplot(222)
  138. plt.contourf(x, y, z)
  139. x = np.repeat(x[np.newaxis], 20, axis=0)
  140. y = np.repeat(y[:, np.newaxis], 20, axis=1)
  141. plt.subplot(223)
  142. plt.contour(x, y, z)
  143. plt.subplot(224)
  144. plt.contourf(x, y, z)
  145. for ax in fig.get_axes():
  146. for label in ax.get_xticklabels():
  147. label.set_ha('right')
  148. label.set_rotation(30)
  149. @image_comparison(['contour_test_label_transforms.png'],
  150. remove_text=True, style='mpl20',
  151. tol=0 if platform.machine() == 'x86_64' else 0.08)
  152. def test_labels():
  153. # Adapted from pylab_examples example code: contour_demo.py
  154. # see issues #2475, #2843, and #2818 for explanation
  155. delta = 0.025
  156. x = np.arange(-3.0, 3.0, delta)
  157. y = np.arange(-2.0, 2.0, delta)
  158. X, Y = np.meshgrid(x, y)
  159. Z1 = np.exp(-(X**2 + Y**2) / 2) / (2 * np.pi)
  160. Z2 = (np.exp(-(((X - 1) / 1.5)**2 + ((Y - 1) / 0.5)**2) / 2) /
  161. (2 * np.pi * 0.5 * 1.5))
  162. # difference of Gaussians
  163. Z = 10.0 * (Z2 - Z1)
  164. fig, ax = plt.subplots(1, 1)
  165. CS = ax.contour(X, Y, Z)
  166. disp_units = [(216, 177), (359, 290), (521, 406)]
  167. data_units = [(-2, .5), (0, -1.5), (2.8, 1)]
  168. CS.clabel()
  169. for x, y in data_units:
  170. CS.add_label_near(x, y, inline=True, transform=None)
  171. for x, y in disp_units:
  172. CS.add_label_near(x, y, inline=True, transform=False)
  173. @image_comparison(['contour_corner_mask_False.png',
  174. 'contour_corner_mask_True.png'],
  175. remove_text=True)
  176. def test_corner_mask():
  177. n = 60
  178. mask_level = 0.95
  179. noise_amp = 1.0
  180. np.random.seed([1])
  181. x, y = np.meshgrid(np.linspace(0, 2.0, n), np.linspace(0, 2.0, n))
  182. z = np.cos(7*x)*np.sin(8*y) + noise_amp*np.random.rand(n, n)
  183. mask = np.random.rand(n, n) >= mask_level
  184. z = np.ma.array(z, mask=mask)
  185. for corner_mask in [False, True]:
  186. plt.figure()
  187. plt.contourf(z, corner_mask=corner_mask)
  188. def test_contourf_decreasing_levels():
  189. # github issue 5477.
  190. z = [[0.1, 0.3], [0.5, 0.7]]
  191. plt.figure()
  192. with pytest.raises(ValueError):
  193. plt.contourf(z, [1.0, 0.0])
  194. def test_contourf_symmetric_locator():
  195. # github issue 7271
  196. z = np.arange(12).reshape((3, 4))
  197. locator = plt.MaxNLocator(nbins=4, symmetric=True)
  198. cs = plt.contourf(z, locator=locator)
  199. assert_array_almost_equal(cs.levels, np.linspace(-12, 12, 5))
  200. @pytest.mark.parametrize("args, cls, message", [
  201. ((), TypeError,
  202. 'function takes exactly 6 arguments (0 given)'),
  203. ((1, 2, 3, 4, 5, 6), ValueError,
  204. 'Expected 2-dimensional array, got 0'),
  205. (([[0]], [[0]], [[]], None, True, 0), ValueError,
  206. 'x, y and z must all be 2D arrays with the same dimensions'),
  207. (([[0]], [[0]], [[0]], None, True, 0), ValueError,
  208. 'x, y and z must all be at least 2x2 arrays'),
  209. ((*[np.arange(4).reshape((2, 2))] * 3, [[0]], True, 0), ValueError,
  210. 'If mask is set it must be a 2D array with the same dimensions as x.'),
  211. ])
  212. def test_internal_cpp_api(args, cls, message): # Github issue 8197.
  213. from matplotlib import _contour # noqa: ensure lazy-loaded module *is* loaded.
  214. with pytest.raises(cls, match=re.escape(message)):
  215. mpl._contour.QuadContourGenerator(*args)
  216. def test_internal_cpp_api_2():
  217. from matplotlib import _contour # noqa: ensure lazy-loaded module *is* loaded.
  218. arr = [[0, 1], [2, 3]]
  219. qcg = mpl._contour.QuadContourGenerator(arr, arr, arr, None, True, 0)
  220. with pytest.raises(
  221. ValueError, match=r'filled contour levels must be increasing'):
  222. qcg.create_filled_contour(1, 0)
  223. def test_circular_contour_warning():
  224. # Check that almost circular contours don't throw a warning
  225. x, y = np.meshgrid(np.linspace(-2, 2, 4), np.linspace(-2, 2, 4))
  226. r = np.hypot(x, y)
  227. plt.figure()
  228. cs = plt.contour(x, y, r)
  229. plt.clabel(cs)
  230. @pytest.mark.parametrize("use_clabeltext, contour_zorder, clabel_zorder",
  231. [(True, 123, 1234), (False, 123, 1234),
  232. (True, 123, None), (False, 123, None)])
  233. def test_clabel_zorder(use_clabeltext, contour_zorder, clabel_zorder):
  234. x, y = np.meshgrid(np.arange(0, 10), np.arange(0, 10))
  235. z = np.max(np.dstack([abs(x), abs(y)]), 2)
  236. fig, (ax1, ax2) = plt.subplots(ncols=2)
  237. cs = ax1.contour(x, y, z, zorder=contour_zorder)
  238. cs_filled = ax2.contourf(x, y, z, zorder=contour_zorder)
  239. clabels1 = cs.clabel(zorder=clabel_zorder, use_clabeltext=use_clabeltext)
  240. clabels2 = cs_filled.clabel(zorder=clabel_zorder,
  241. use_clabeltext=use_clabeltext)
  242. if clabel_zorder is None:
  243. expected_clabel_zorder = 2+contour_zorder
  244. else:
  245. expected_clabel_zorder = clabel_zorder
  246. for clabel in clabels1:
  247. assert clabel.get_zorder() == expected_clabel_zorder
  248. for clabel in clabels2:
  249. assert clabel.get_zorder() == expected_clabel_zorder
  250. @image_comparison(['contour_log_extension.png'],
  251. remove_text=True, style='mpl20')
  252. def test_contourf_log_extension():
  253. # Test that contourf with lognorm is extended correctly
  254. fig = plt.figure(figsize=(10, 5))
  255. fig.subplots_adjust(left=0.05, right=0.95)
  256. ax1 = fig.add_subplot(131)
  257. ax2 = fig.add_subplot(132)
  258. ax3 = fig.add_subplot(133)
  259. # make data set with large range e.g. between 1e-8 and 1e10
  260. data_exp = np.linspace(-7.5, 9.5, 1200)
  261. data = np.power(10, data_exp).reshape(30, 40)
  262. # make manual levels e.g. between 1e-4 and 1e-6
  263. levels_exp = np.arange(-4., 7.)
  264. levels = np.power(10., levels_exp)
  265. # original data
  266. c1 = ax1.contourf(data,
  267. norm=LogNorm(vmin=data.min(), vmax=data.max()))
  268. # just show data in levels
  269. c2 = ax2.contourf(data, levels=levels,
  270. norm=LogNorm(vmin=levels.min(), vmax=levels.max()),
  271. extend='neither')
  272. # extend data from levels
  273. c3 = ax3.contourf(data, levels=levels,
  274. norm=LogNorm(vmin=levels.min(), vmax=levels.max()),
  275. extend='both')
  276. cb = plt.colorbar(c1, ax=ax1)
  277. assert cb.ax.get_ylim() == (1e-8, 1e10)
  278. cb = plt.colorbar(c2, ax=ax2)
  279. assert cb.ax.get_ylim() == (1e-4, 1e6)
  280. cb = plt.colorbar(c3, ax=ax3)
  281. assert_array_almost_equal(
  282. cb.ax.get_ylim(), [3.162277660168379e-05, 3162277.660168383], 2)
  283. @image_comparison(['contour_addlines.png'],
  284. remove_text=True, style='mpl20', tol=0.03)
  285. # tolerance is because image changed minutely when tick finding on
  286. # colorbars was cleaned up...
  287. def test_contour_addlines():
  288. fig, ax = plt.subplots()
  289. np.random.seed(19680812)
  290. X = np.random.rand(10, 10)*10000
  291. pcm = ax.pcolormesh(X)
  292. # add 1000 to make colors visible...
  293. cont = ax.contour(X+1000)
  294. cb = fig.colorbar(pcm)
  295. cb.add_lines(cont)
  296. assert_array_almost_equal(cb.ax.get_ylim(), [114.3091, 9972.30735], 3)
  297. @image_comparison(baseline_images=['contour_uneven'],
  298. extensions=['png'], remove_text=True, style='mpl20')
  299. def test_contour_uneven():
  300. z = np.arange(24).reshape(4, 6)
  301. fig, axs = plt.subplots(1, 2)
  302. ax = axs[0]
  303. cs = ax.contourf(z, levels=[2, 4, 6, 10, 20])
  304. fig.colorbar(cs, ax=ax, spacing='proportional')
  305. ax = axs[1]
  306. cs = ax.contourf(z, levels=[2, 4, 6, 10, 20])
  307. fig.colorbar(cs, ax=ax, spacing='uniform')
  308. @pytest.mark.parametrize(
  309. "rc_lines_linewidth, rc_contour_linewidth, call_linewidths, expected", [
  310. (1.23, None, None, 1.23),
  311. (1.23, 4.24, None, 4.24),
  312. (1.23, 4.24, 5.02, 5.02)
  313. ])
  314. def test_contour_linewidth(
  315. rc_lines_linewidth, rc_contour_linewidth, call_linewidths, expected):
  316. with rc_context(rc={"lines.linewidth": rc_lines_linewidth,
  317. "contour.linewidth": rc_contour_linewidth}):
  318. fig, ax = plt.subplots()
  319. X = np.arange(4*3).reshape(4, 3)
  320. cs = ax.contour(X, linewidths=call_linewidths)
  321. assert cs.tlinewidths[0][0] == expected