test_artist.py 8.8 KB

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