test_polar.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528
  1. import numpy as np
  2. from numpy.testing import assert_allclose
  3. import pytest
  4. import matplotlib as mpl
  5. from matplotlib import pyplot as plt
  6. from matplotlib.testing.decorators import image_comparison, check_figures_equal
  7. @image_comparison(['polar_axes.png'], style='default', tol=0.012)
  8. def test_polar_annotations():
  9. # You can specify the xypoint and the xytext in different positions and
  10. # coordinate systems, and optionally turn on a connecting line and mark the
  11. # point with a marker. Annotations work on polar axes too. In the example
  12. # below, the xy point is in native coordinates (xycoords defaults to
  13. # 'data'). For a polar axes, this is in (theta, radius) space. The text
  14. # in this example is placed in the fractional figure coordinate system.
  15. # Text keyword args like horizontal and vertical alignment are respected.
  16. # Setup some data
  17. r = np.arange(0.0, 1.0, 0.001)
  18. theta = 2.0 * 2.0 * np.pi * r
  19. fig = plt.figure()
  20. ax = fig.add_subplot(polar=True)
  21. line, = ax.plot(theta, r, color='#ee8d18', lw=3)
  22. line, = ax.plot((0, 0), (0, 1), color="#0000ff", lw=1)
  23. ind = 800
  24. thisr, thistheta = r[ind], theta[ind]
  25. ax.plot([thistheta], [thisr], 'o')
  26. ax.annotate('a polar annotation',
  27. xy=(thistheta, thisr), # theta, radius
  28. xytext=(0.05, 0.05), # fraction, fraction
  29. textcoords='figure fraction',
  30. arrowprops=dict(facecolor='black', shrink=0.05),
  31. horizontalalignment='left',
  32. verticalalignment='baseline',
  33. )
  34. ax.tick_params(axis='x', tick1On=True, tick2On=True, direction='out')
  35. @image_comparison(['polar_coords.png'], style='default', remove_text=True,
  36. tol=0.014)
  37. def test_polar_coord_annotations():
  38. # You can also use polar notation on a cartesian axes. Here the native
  39. # coordinate system ('data') is cartesian, so you need to specify the
  40. # xycoords and textcoords as 'polar' if you want to use (theta, radius).
  41. el = mpl.patches.Ellipse((0, 0), 10, 20, facecolor='r', alpha=0.5)
  42. fig = plt.figure()
  43. ax = fig.add_subplot(aspect='equal')
  44. ax.add_artist(el)
  45. el.set_clip_box(ax.bbox)
  46. ax.annotate('the top',
  47. xy=(np.pi/2., 10.), # theta, radius
  48. xytext=(np.pi/3, 20.), # theta, radius
  49. xycoords='polar',
  50. textcoords='polar',
  51. arrowprops=dict(facecolor='black', shrink=0.05),
  52. horizontalalignment='left',
  53. verticalalignment='baseline',
  54. clip_on=True, # clip to the axes bounding box
  55. )
  56. ax.set_xlim(-20, 20)
  57. ax.set_ylim(-20, 20)
  58. @image_comparison(['polar_alignment.png'])
  59. def test_polar_alignment():
  60. # Test changing the vertical/horizontal alignment of a polar graph.
  61. angles = np.arange(0, 360, 90)
  62. grid_values = [0, 0.2, 0.4, 0.6, 0.8, 1]
  63. fig = plt.figure()
  64. rect = [0.1, 0.1, 0.8, 0.8]
  65. horizontal = fig.add_axes(rect, polar=True, label='horizontal')
  66. horizontal.set_thetagrids(angles)
  67. vertical = fig.add_axes(rect, polar=True, label='vertical')
  68. vertical.patch.set_visible(False)
  69. for i in range(2):
  70. fig.axes[i].set_rgrids(
  71. grid_values, angle=angles[i],
  72. horizontalalignment='left', verticalalignment='top')
  73. def test_polar_twice():
  74. fig = plt.figure()
  75. plt.polar([1, 2], [.1, .2])
  76. plt.polar([3, 4], [.3, .4])
  77. assert len(fig.axes) == 1, 'More than one polar Axes created.'
  78. @check_figures_equal(extensions=['png'])
  79. def test_polar_wrap(fig_test, fig_ref):
  80. ax = fig_test.add_subplot(projection="polar")
  81. ax.plot(np.deg2rad([179, -179]), [0.2, 0.1])
  82. ax.plot(np.deg2rad([2, -2]), [0.2, 0.1])
  83. ax = fig_ref.add_subplot(projection="polar")
  84. ax.plot(np.deg2rad([179, 181]), [0.2, 0.1])
  85. ax.plot(np.deg2rad([2, 358]), [0.2, 0.1])
  86. @check_figures_equal(extensions=['png'])
  87. def test_polar_units_1(fig_test, fig_ref):
  88. import matplotlib.testing.jpl_units as units
  89. units.register()
  90. xs = [30.0, 45.0, 60.0, 90.0]
  91. ys = [1.0, 2.0, 3.0, 4.0]
  92. plt.figure(fig_test.number)
  93. plt.polar([x * units.deg for x in xs], ys)
  94. ax = fig_ref.add_subplot(projection="polar")
  95. ax.plot(np.deg2rad(xs), ys)
  96. ax.set(xlabel="deg")
  97. @check_figures_equal(extensions=['png'])
  98. def test_polar_units_2(fig_test, fig_ref):
  99. import matplotlib.testing.jpl_units as units
  100. units.register()
  101. xs = [30.0, 45.0, 60.0, 90.0]
  102. xs_deg = [x * units.deg for x in xs]
  103. ys = [1.0, 2.0, 3.0, 4.0]
  104. ys_km = [y * units.km for y in ys]
  105. plt.figure(fig_test.number)
  106. # test {theta,r}units.
  107. plt.polar(xs_deg, ys_km, thetaunits="rad", runits="km")
  108. assert isinstance(plt.gca().xaxis.get_major_formatter(),
  109. units.UnitDblFormatter)
  110. ax = fig_ref.add_subplot(projection="polar")
  111. ax.plot(np.deg2rad(xs), ys)
  112. ax.xaxis.set_major_formatter(mpl.ticker.FuncFormatter("{:.12}".format))
  113. ax.set(xlabel="rad", ylabel="km")
  114. @image_comparison(['polar_rmin.png'], style='default')
  115. def test_polar_rmin():
  116. r = np.arange(0, 3.0, 0.01)
  117. theta = 2*np.pi*r
  118. fig = plt.figure()
  119. ax = fig.add_axes([0.1, 0.1, 0.8, 0.8], polar=True)
  120. ax.plot(theta, r)
  121. ax.set_rmax(2.0)
  122. ax.set_rmin(0.5)
  123. @image_comparison(['polar_negative_rmin.png'], style='default')
  124. def test_polar_negative_rmin():
  125. r = np.arange(-3.0, 0.0, 0.01)
  126. theta = 2*np.pi*r
  127. fig = plt.figure()
  128. ax = fig.add_axes([0.1, 0.1, 0.8, 0.8], polar=True)
  129. ax.plot(theta, r)
  130. ax.set_rmax(0.0)
  131. ax.set_rmin(-3.0)
  132. @image_comparison(['polar_rorigin.png'], style='default')
  133. def test_polar_rorigin():
  134. r = np.arange(0, 3.0, 0.01)
  135. theta = 2*np.pi*r
  136. fig = plt.figure()
  137. ax = fig.add_axes([0.1, 0.1, 0.8, 0.8], polar=True)
  138. ax.plot(theta, r)
  139. ax.set_rmax(2.0)
  140. ax.set_rmin(0.5)
  141. ax.set_rorigin(0.0)
  142. @image_comparison(['polar_invertedylim.png'], style='default')
  143. def test_polar_invertedylim():
  144. fig = plt.figure()
  145. ax = fig.add_axes([0.1, 0.1, 0.8, 0.8], polar=True)
  146. ax.set_ylim(2, 0)
  147. @image_comparison(['polar_invertedylim_rorigin.png'], style='default')
  148. def test_polar_invertedylim_rorigin():
  149. fig = plt.figure()
  150. ax = fig.add_axes([0.1, 0.1, 0.8, 0.8], polar=True)
  151. ax.yaxis.set_inverted(True)
  152. # Set the rlims to inverted (2, 0) without calling set_rlim, to check that
  153. # viewlims are correctly unstaled before draw()ing.
  154. ax.plot([0, 0], [0, 2], c="none")
  155. ax.margins(0)
  156. ax.set_rorigin(3)
  157. @image_comparison(['polar_theta_position.png'], style='default')
  158. def test_polar_theta_position():
  159. r = np.arange(0, 3.0, 0.01)
  160. theta = 2*np.pi*r
  161. fig = plt.figure()
  162. ax = fig.add_axes([0.1, 0.1, 0.8, 0.8], polar=True)
  163. ax.plot(theta, r)
  164. ax.set_theta_zero_location("NW", 30)
  165. ax.set_theta_direction('clockwise')
  166. @image_comparison(['polar_rlabel_position.png'], style='default')
  167. def test_polar_rlabel_position():
  168. fig = plt.figure()
  169. ax = fig.add_subplot(projection='polar')
  170. ax.set_rlabel_position(315)
  171. ax.tick_params(rotation='auto')
  172. @image_comparison(['polar_title_position.png'], style='mpl20')
  173. def test_polar_title_position():
  174. fig = plt.figure()
  175. ax = fig.add_subplot(projection='polar')
  176. ax.set_title('foo')
  177. @image_comparison(['polar_theta_wedge.png'], style='default')
  178. def test_polar_theta_limits():
  179. r = np.arange(0, 3.0, 0.01)
  180. theta = 2*np.pi*r
  181. theta_mins = np.arange(15.0, 361.0, 90.0)
  182. theta_maxs = np.arange(50.0, 361.0, 90.0)
  183. DIRECTIONS = ('out', 'in', 'inout')
  184. fig, axs = plt.subplots(len(theta_mins), len(theta_maxs),
  185. subplot_kw={'polar': True},
  186. figsize=(8, 6))
  187. for i, start in enumerate(theta_mins):
  188. for j, end in enumerate(theta_maxs):
  189. ax = axs[i, j]
  190. ax.plot(theta, r)
  191. if start < end:
  192. ax.set_thetamin(start)
  193. ax.set_thetamax(end)
  194. else:
  195. # Plot with clockwise orientation instead.
  196. ax.set_thetamin(end)
  197. ax.set_thetamax(start)
  198. ax.set_theta_direction('clockwise')
  199. ax.tick_params(tick1On=True, tick2On=True,
  200. direction=DIRECTIONS[i % len(DIRECTIONS)],
  201. rotation='auto')
  202. ax.yaxis.set_tick_params(label2On=True, rotation='auto')
  203. ax.xaxis.get_major_locator().base.set_params( # backcompat
  204. steps=[1, 2, 2.5, 5, 10])
  205. @check_figures_equal(extensions=["png"])
  206. def test_polar_rlim(fig_test, fig_ref):
  207. ax = fig_test.subplots(subplot_kw={'polar': True})
  208. ax.set_rlim(top=10)
  209. ax.set_rlim(bottom=.5)
  210. ax = fig_ref.subplots(subplot_kw={'polar': True})
  211. ax.set_rmax(10.)
  212. ax.set_rmin(.5)
  213. @check_figures_equal(extensions=["png"])
  214. def test_polar_rlim_bottom(fig_test, fig_ref):
  215. ax = fig_test.subplots(subplot_kw={'polar': True})
  216. ax.set_rlim(bottom=[.5, 10])
  217. ax = fig_ref.subplots(subplot_kw={'polar': True})
  218. ax.set_rmax(10.)
  219. ax.set_rmin(.5)
  220. def test_polar_rlim_zero():
  221. ax = plt.figure().add_subplot(projection='polar')
  222. ax.plot(np.arange(10), np.arange(10) + .01)
  223. assert ax.get_ylim()[0] == 0
  224. def test_polar_no_data():
  225. plt.subplot(projection="polar")
  226. ax = plt.gca()
  227. assert ax.get_rmin() == 0 and ax.get_rmax() == 1
  228. plt.close("all")
  229. # Used to behave differently (by triggering an autoscale with no data).
  230. plt.polar()
  231. ax = plt.gca()
  232. assert ax.get_rmin() == 0 and ax.get_rmax() == 1
  233. def test_polar_default_log_lims():
  234. plt.subplot(projection='polar')
  235. ax = plt.gca()
  236. ax.set_rscale('log')
  237. assert ax.get_rmin() > 0
  238. def test_polar_not_datalim_adjustable():
  239. ax = plt.figure().add_subplot(projection="polar")
  240. with pytest.raises(ValueError):
  241. ax.set_adjustable("datalim")
  242. def test_polar_gridlines():
  243. fig = plt.figure()
  244. ax = fig.add_subplot(polar=True)
  245. # make all major grid lines lighter, only x grid lines set in 2.1.0
  246. ax.grid(alpha=0.2)
  247. # hide y tick labels, no effect in 2.1.0
  248. plt.setp(ax.yaxis.get_ticklabels(), visible=False)
  249. fig.canvas.draw()
  250. assert ax.xaxis.majorTicks[0].gridline.get_alpha() == .2
  251. assert ax.yaxis.majorTicks[0].gridline.get_alpha() == .2
  252. def test_get_tightbbox_polar():
  253. fig, ax = plt.subplots(subplot_kw={'projection': 'polar'})
  254. fig.canvas.draw()
  255. bb = ax.get_tightbbox(fig.canvas.get_renderer())
  256. assert_allclose(
  257. bb.extents, [107.7778, 29.2778, 539.7847, 450.7222], rtol=1e-03)
  258. @check_figures_equal(extensions=["png"])
  259. def test_polar_interpolation_steps_constant_r(fig_test, fig_ref):
  260. # Check that an extra half-turn doesn't make any difference -- modulo
  261. # antialiasing, which we disable here.
  262. p1 = (fig_test.add_subplot(121, projection="polar")
  263. .bar([0], [1], 3*np.pi, edgecolor="none", antialiased=False))
  264. p2 = (fig_test.add_subplot(122, projection="polar")
  265. .bar([0], [1], -3*np.pi, edgecolor="none", antialiased=False))
  266. p3 = (fig_ref.add_subplot(121, projection="polar")
  267. .bar([0], [1], 2*np.pi, edgecolor="none", antialiased=False))
  268. p4 = (fig_ref.add_subplot(122, projection="polar")
  269. .bar([0], [1], -2*np.pi, edgecolor="none", antialiased=False))
  270. @check_figures_equal(extensions=["png"])
  271. def test_polar_interpolation_steps_variable_r(fig_test, fig_ref):
  272. l, = fig_test.add_subplot(projection="polar").plot([0, np.pi/2], [1, 2])
  273. l.get_path()._interpolation_steps = 100
  274. fig_ref.add_subplot(projection="polar").plot(
  275. np.linspace(0, np.pi/2, 101), np.linspace(1, 2, 101))
  276. def test_thetalim_valid_invalid():
  277. ax = plt.subplot(projection='polar')
  278. ax.set_thetalim(0, 2 * np.pi) # doesn't raise.
  279. ax.set_thetalim(thetamin=800, thetamax=440) # doesn't raise.
  280. with pytest.raises(ValueError,
  281. match='angle range must be less than a full circle'):
  282. ax.set_thetalim(0, 3 * np.pi)
  283. with pytest.raises(ValueError,
  284. match='angle range must be less than a full circle'):
  285. ax.set_thetalim(thetamin=800, thetamax=400)
  286. def test_thetalim_args():
  287. ax = plt.subplot(projection='polar')
  288. ax.set_thetalim(0, 1)
  289. assert tuple(np.radians((ax.get_thetamin(), ax.get_thetamax()))) == (0, 1)
  290. ax.set_thetalim((2, 3))
  291. assert tuple(np.radians((ax.get_thetamin(), ax.get_thetamax()))) == (2, 3)
  292. def test_default_thetalocator():
  293. # Ideally we would check AAAABBC, but the smallest axes currently puts a
  294. # single tick at 150° because MaxNLocator doesn't have a way to accept 15°
  295. # while rejecting 150°.
  296. fig, axs = plt.subplot_mosaic(
  297. "AAAABB.", subplot_kw={"projection": "polar"})
  298. for ax in axs.values():
  299. ax.set_thetalim(0, np.pi)
  300. for ax in axs.values():
  301. ticklocs = np.degrees(ax.xaxis.get_majorticklocs()).tolist()
  302. assert pytest.approx(90) in ticklocs
  303. assert pytest.approx(100) not in ticklocs
  304. def test_axvspan():
  305. ax = plt.subplot(projection="polar")
  306. span = ax.axvspan(0, np.pi/4)
  307. assert span.get_path()._interpolation_steps > 1
  308. @check_figures_equal(extensions=["png"])
  309. def test_remove_shared_polar(fig_ref, fig_test):
  310. # Removing shared polar axes used to crash. Test removing them, keeping in
  311. # both cases just the lower left axes of a grid to avoid running into a
  312. # separate issue (now being fixed) of ticklabel visibility for shared axes.
  313. axs = fig_ref.subplots(
  314. 2, 2, sharex=True, subplot_kw={"projection": "polar"})
  315. for i in [0, 1, 3]:
  316. axs.flat[i].remove()
  317. axs = fig_test.subplots(
  318. 2, 2, sharey=True, subplot_kw={"projection": "polar"})
  319. for i in [0, 1, 3]:
  320. axs.flat[i].remove()
  321. def test_shared_polar_keeps_ticklabels():
  322. fig, axs = plt.subplots(
  323. 2, 2, subplot_kw={"projection": "polar"}, sharex=True, sharey=True)
  324. fig.canvas.draw()
  325. assert axs[0, 1].xaxis.majorTicks[0].get_visible()
  326. assert axs[0, 1].yaxis.majorTicks[0].get_visible()
  327. fig, axs = plt.subplot_mosaic(
  328. "ab\ncd", subplot_kw={"projection": "polar"}, sharex=True, sharey=True)
  329. fig.canvas.draw()
  330. assert axs["b"].xaxis.majorTicks[0].get_visible()
  331. assert axs["b"].yaxis.majorTicks[0].get_visible()
  332. def test_axvline_axvspan_do_not_modify_rlims():
  333. ax = plt.subplot(projection="polar")
  334. ax.axvspan(0, 1)
  335. ax.axvline(.5)
  336. ax.plot([.1, .2])
  337. assert ax.get_ylim() == (0, .2)
  338. def test_cursor_precision():
  339. ax = plt.subplot(projection="polar")
  340. # Higher radii correspond to higher theta-precisions.
  341. assert ax.format_coord(0, 0.005) == "θ=0.0π (0°), r=0.005"
  342. assert ax.format_coord(0, .1) == "θ=0.00π (0°), r=0.100"
  343. assert ax.format_coord(0, 1) == "θ=0.000π (0.0°), r=1.000"
  344. assert ax.format_coord(1, 0.005) == "θ=0.3π (57°), r=0.005"
  345. assert ax.format_coord(1, .1) == "θ=0.32π (57°), r=0.100"
  346. assert ax.format_coord(1, 1) == "θ=0.318π (57.3°), r=1.000"
  347. assert ax.format_coord(2, 0.005) == "θ=0.6π (115°), r=0.005"
  348. assert ax.format_coord(2, .1) == "θ=0.64π (115°), r=0.100"
  349. assert ax.format_coord(2, 1) == "θ=0.637π (114.6°), r=1.000"
  350. def test_custom_fmt_data():
  351. ax = plt.subplot(projection="polar")
  352. def millions(x):
  353. return '$%1.1fM' % (x*1e-6)
  354. # Test only x formatter
  355. ax.fmt_xdata = None
  356. ax.fmt_ydata = millions
  357. assert ax.format_coord(12, 2e7) == "θ=3.8197186342π (687.54935416°), r=$20.0M"
  358. assert ax.format_coord(1234, 2e6) == "θ=392.794399551π (70702.9919191°), r=$2.0M"
  359. assert ax.format_coord(3, 100) == "θ=0.95493π (171.887°), r=$0.0M"
  360. # Test only y formatter
  361. ax.fmt_xdata = millions
  362. ax.fmt_ydata = None
  363. assert ax.format_coord(2e5, 1) == "θ=$0.2M, r=1.000"
  364. assert ax.format_coord(1, .1) == "θ=$0.0M, r=0.100"
  365. assert ax.format_coord(1e6, 0.005) == "θ=$1.0M, r=0.005"
  366. # Test both x and y formatters
  367. ax.fmt_xdata = millions
  368. ax.fmt_ydata = millions
  369. assert ax.format_coord(2e6, 2e4*3e5) == "θ=$2.0M, r=$6000.0M"
  370. assert ax.format_coord(1e18, 12891328123) == "θ=$1000000000000.0M, r=$12891.3M"
  371. assert ax.format_coord(63**7, 1081968*1024) == "θ=$3938980.6M, r=$1107.9M"
  372. @image_comparison(['polar_log.png'], style='default')
  373. def test_polar_log():
  374. fig = plt.figure()
  375. ax = fig.add_subplot(polar=True)
  376. ax.set_rscale('log')
  377. ax.set_rlim(1, 1000)
  378. n = 100
  379. ax.plot(np.linspace(0, 2 * np.pi, n), np.logspace(0, 2, n))
  380. @check_figures_equal()
  381. def test_polar_log_rorigin(fig_ref, fig_test):
  382. # Test that equivalent linear and log radial settings give the same axes patch
  383. # and spines.
  384. ax_ref = fig_ref.add_subplot(projection='polar', facecolor='red')
  385. ax_ref.set_rlim(0, 2)
  386. ax_ref.set_rorigin(-3)
  387. ax_ref.set_rticks(np.linspace(0, 2, 5))
  388. ax_test = fig_test.add_subplot(projection='polar', facecolor='red')
  389. ax_test.set_rscale('log')
  390. ax_test.set_rlim(1, 100)
  391. ax_test.set_rorigin(10**-3)
  392. ax_test.set_rticks(np.logspace(0, 2, 5))
  393. for ax in ax_ref, ax_test:
  394. # Radial tick labels should be the only difference, so turn them off.
  395. ax.tick_params(labelleft=False)
  396. def test_polar_neg_theta_lims():
  397. fig = plt.figure()
  398. ax = fig.add_subplot(projection='polar')
  399. ax.set_thetalim(-np.pi, np.pi)
  400. labels = [l.get_text() for l in ax.xaxis.get_ticklabels()]
  401. assert labels == ['-180°', '-135°', '-90°', '-45°', '0°', '45°', '90°', '135°']
  402. @pytest.mark.parametrize("order", ["before", "after"])
  403. @image_comparison(baseline_images=['polar_errorbar.png'], remove_text=True,
  404. style='mpl20')
  405. def test_polar_errorbar(order):
  406. theta = np.arange(0, 2 * np.pi, np.pi / 8)
  407. r = theta / np.pi / 2 + 0.5
  408. fig = plt.figure(figsize=(5, 5))
  409. ax = fig.add_subplot(projection='polar')
  410. if order == "before":
  411. ax.set_theta_zero_location("N")
  412. ax.set_theta_direction(-1)
  413. ax.errorbar(theta, r, xerr=0.1, yerr=0.1, capsize=7, fmt="o", c="seagreen")
  414. else:
  415. ax.errorbar(theta, r, xerr=0.1, yerr=0.1, capsize=7, fmt="o", c="seagreen")
  416. ax.set_theta_zero_location("N")
  417. ax.set_theta_direction(-1)