test_artist.py 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287
  1. import io
  2. from itertools import chain
  3. import warnings
  4. import numpy as np
  5. import pytest
  6. import matplotlib.pyplot as plt
  7. import matplotlib.patches as mpatches
  8. import matplotlib.lines as mlines
  9. import matplotlib.path as mpath
  10. import matplotlib.transforms as mtransforms
  11. import matplotlib.collections as mcollections
  12. import matplotlib.artist as martist
  13. from matplotlib.testing.decorators import image_comparison
  14. def test_patch_transform_of_none():
  15. # tests the behaviour of patches added to an Axes with various transform
  16. # specifications
  17. ax = plt.axes()
  18. ax.set_xlim([1, 3])
  19. ax.set_ylim([1, 3])
  20. # Draw an ellipse over data coord (2, 2) by specifying device coords.
  21. xy_data = (2, 2)
  22. xy_pix = ax.transData.transform(xy_data)
  23. # Not providing a transform of None puts the ellipse in data coordinates .
  24. e = mpatches.Ellipse(xy_data, width=1, height=1, fc='yellow', alpha=0.5)
  25. ax.add_patch(e)
  26. assert e._transform == ax.transData
  27. # Providing a transform of None puts the ellipse in device coordinates.
  28. e = mpatches.Ellipse(xy_pix, width=120, height=120, fc='coral',
  29. transform=None, alpha=0.5)
  30. assert e.is_transform_set()
  31. ax.add_patch(e)
  32. assert isinstance(e._transform, mtransforms.IdentityTransform)
  33. # Providing an IdentityTransform puts the ellipse in device coordinates.
  34. e = mpatches.Ellipse(xy_pix, width=100, height=100,
  35. transform=mtransforms.IdentityTransform(), alpha=0.5)
  36. ax.add_patch(e)
  37. assert isinstance(e._transform, mtransforms.IdentityTransform)
  38. # Not providing a transform, and then subsequently "get_transform" should
  39. # not mean that "is_transform_set".
  40. e = mpatches.Ellipse(xy_pix, width=120, height=120, fc='coral',
  41. alpha=0.5)
  42. intermediate_transform = e.get_transform()
  43. assert not e.is_transform_set()
  44. ax.add_patch(e)
  45. assert e.get_transform() != intermediate_transform
  46. assert e.is_transform_set()
  47. assert e._transform == ax.transData
  48. def test_collection_transform_of_none():
  49. # tests the behaviour of collections added to an Axes with various
  50. # transform specifications
  51. ax = plt.axes()
  52. ax.set_xlim([1, 3])
  53. ax.set_ylim([1, 3])
  54. # draw an ellipse over data coord (2, 2) by specifying device coords
  55. xy_data = (2, 2)
  56. xy_pix = ax.transData.transform(xy_data)
  57. # not providing a transform of None puts the ellipse in data coordinates
  58. e = mpatches.Ellipse(xy_data, width=1, height=1)
  59. c = mcollections.PatchCollection([e], facecolor='yellow', alpha=0.5)
  60. ax.add_collection(c)
  61. # the collection should be in data coordinates
  62. assert c.get_offset_transform() + c.get_transform() == ax.transData
  63. # providing a transform of None puts the ellipse in device coordinates
  64. e = mpatches.Ellipse(xy_pix, width=120, height=120)
  65. c = mcollections.PatchCollection([e], facecolor='coral',
  66. alpha=0.5)
  67. c.set_transform(None)
  68. ax.add_collection(c)
  69. assert isinstance(c.get_transform(), mtransforms.IdentityTransform)
  70. # providing an IdentityTransform puts the ellipse in device coordinates
  71. e = mpatches.Ellipse(xy_pix, width=100, height=100)
  72. c = mcollections.PatchCollection([e],
  73. transform=mtransforms.IdentityTransform(),
  74. alpha=0.5)
  75. ax.add_collection(c)
  76. assert isinstance(c._transOffset, mtransforms.IdentityTransform)
  77. @image_comparison(["clip_path_clipping"], remove_text=True)
  78. def test_clipping():
  79. exterior = mpath.Path.unit_rectangle().deepcopy()
  80. exterior.vertices *= 4
  81. exterior.vertices -= 2
  82. interior = mpath.Path.unit_circle().deepcopy()
  83. interior.vertices = interior.vertices[::-1]
  84. clip_path = mpath.Path(vertices=np.concatenate([exterior.vertices,
  85. interior.vertices]),
  86. codes=np.concatenate([exterior.codes,
  87. interior.codes]))
  88. star = mpath.Path.unit_regular_star(6).deepcopy()
  89. star.vertices *= 2.6
  90. ax1 = plt.subplot(121)
  91. col = mcollections.PathCollection([star], lw=5, edgecolor='blue',
  92. facecolor='red', alpha=0.7, hatch='*')
  93. col.set_clip_path(clip_path, ax1.transData)
  94. ax1.add_collection(col)
  95. ax2 = plt.subplot(122, sharex=ax1, sharey=ax1)
  96. patch = mpatches.PathPatch(star, lw=5, edgecolor='blue', facecolor='red',
  97. alpha=0.7, hatch='*')
  98. patch.set_clip_path(clip_path, ax2.transData)
  99. ax2.add_patch(patch)
  100. ax1.set_xlim([-3, 3])
  101. ax1.set_ylim([-3, 3])
  102. def test_cull_markers():
  103. x = np.random.random(20000)
  104. y = np.random.random(20000)
  105. fig, ax = plt.subplots()
  106. ax.plot(x, y, 'k.')
  107. ax.set_xlim(2, 3)
  108. pdf = io.BytesIO()
  109. fig.savefig(pdf, format="pdf")
  110. assert len(pdf.getvalue()) < 8000
  111. svg = io.BytesIO()
  112. fig.savefig(svg, format="svg")
  113. assert len(svg.getvalue()) < 20000
  114. @image_comparison(['hatching'], remove_text=True, style='default')
  115. def test_hatching():
  116. fig, ax = plt.subplots(1, 1)
  117. # Default hatch color.
  118. rect1 = mpatches.Rectangle((0, 0), 3, 4, hatch='/')
  119. ax.add_patch(rect1)
  120. rect2 = mcollections.RegularPolyCollection(4, sizes=[16000],
  121. offsets=[(1.5, 6.5)],
  122. transOffset=ax.transData,
  123. hatch='/')
  124. ax.add_collection(rect2)
  125. # Ensure edge color is not applied to hatching.
  126. rect3 = mpatches.Rectangle((4, 0), 3, 4, hatch='/', edgecolor='C1')
  127. ax.add_patch(rect3)
  128. rect4 = mcollections.RegularPolyCollection(4, sizes=[16000],
  129. offsets=[(5.5, 6.5)],
  130. transOffset=ax.transData,
  131. hatch='/', edgecolor='C1')
  132. ax.add_collection(rect4)
  133. ax.set_xlim(0, 7)
  134. ax.set_ylim(0, 9)
  135. def test_remove():
  136. fig, ax = plt.subplots()
  137. im = ax.imshow(np.arange(36).reshape(6, 6))
  138. ln, = ax.plot(range(5))
  139. assert fig.stale
  140. assert ax.stale
  141. fig.canvas.draw()
  142. assert not fig.stale
  143. assert not ax.stale
  144. assert not ln.stale
  145. assert im in ax._mouseover_set
  146. assert ln not in ax._mouseover_set
  147. assert im.axes is ax
  148. im.remove()
  149. ln.remove()
  150. for art in [im, ln]:
  151. assert art.axes is None
  152. assert art.figure is None
  153. assert im not in ax._mouseover_set
  154. assert fig.stale
  155. assert ax.stale
  156. @image_comparison(["default_edges.png"], remove_text=True, style='default')
  157. def test_default_edges():
  158. # Remove this line when this test image is regenerated.
  159. plt.rcParams['text.kerning_factor'] = 6
  160. fig, [[ax1, ax2], [ax3, ax4]] = plt.subplots(2, 2)
  161. ax1.plot(np.arange(10), np.arange(10), 'x',
  162. np.arange(10) + 1, np.arange(10), 'o')
  163. ax2.bar(np.arange(10), np.arange(10), align='edge')
  164. ax3.text(0, 0, "BOX", size=24, bbox=dict(boxstyle='sawtooth'))
  165. ax3.set_xlim((-1, 1))
  166. ax3.set_ylim((-1, 1))
  167. pp1 = mpatches.PathPatch(
  168. mpath.Path([(0, 0), (1, 0), (1, 1), (0, 0)],
  169. [mpath.Path.MOVETO, mpath.Path.CURVE3,
  170. mpath.Path.CURVE3, mpath.Path.CLOSEPOLY]),
  171. fc="none", transform=ax4.transData)
  172. ax4.add_patch(pp1)
  173. def test_properties():
  174. ln = mlines.Line2D([], [])
  175. with warnings.catch_warnings(record=True) as w:
  176. # Cause all warnings to always be triggered.
  177. warnings.simplefilter("always")
  178. ln.properties()
  179. assert len(w) == 0
  180. def test_setp():
  181. # Check empty list
  182. plt.setp([])
  183. plt.setp([[]])
  184. # Check arbitrary iterables
  185. fig, ax = plt.subplots()
  186. lines1 = ax.plot(range(3))
  187. lines2 = ax.plot(range(3))
  188. martist.setp(chain(lines1, lines2), 'lw', 5)
  189. plt.setp(ax.spines.values(), color='green')
  190. # Check *file* argument
  191. sio = io.StringIO()
  192. plt.setp(lines1, 'zorder', file=sio)
  193. assert sio.getvalue() == ' zorder: float\n'
  194. def test_None_zorder():
  195. fig, ax = plt.subplots()
  196. ln, = ax.plot(range(5), zorder=None)
  197. assert ln.get_zorder() == mlines.Line2D.zorder
  198. ln.set_zorder(123456)
  199. assert ln.get_zorder() == 123456
  200. ln.set_zorder(None)
  201. assert ln.get_zorder() == mlines.Line2D.zorder
  202. @pytest.mark.parametrize('accept_clause, expected', [
  203. ('', 'unknown'),
  204. ("ACCEPTS: [ '-' | '--' | '-.' ]", "[ '-' | '--' | '-.' ]"),
  205. ('ACCEPTS: Some description.', 'Some description.'),
  206. ('.. ACCEPTS: Some description.', 'Some description.'),
  207. ('arg : int', 'int'),
  208. ('*arg : int', 'int'),
  209. ('arg : int\nACCEPTS: Something else.', 'Something else. '),
  210. ])
  211. def test_artist_inspector_get_valid_values(accept_clause, expected):
  212. class TestArtist(martist.Artist):
  213. def set_f(self, arg):
  214. pass
  215. TestArtist.set_f.__doc__ = """
  216. Some text.
  217. %s
  218. """ % accept_clause
  219. valid_values = martist.ArtistInspector(TestArtist).get_valid_values('f')
  220. assert valid_values == expected
  221. def test_artist_inspector_get_aliases():
  222. # test the correct format and type of get_aliases method
  223. ai = martist.ArtistInspector(mlines.Line2D)
  224. aliases = ai.get_aliases()
  225. assert aliases["linewidth"] == {"lw"}