test_widgets.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415
  1. import matplotlib.widgets as widgets
  2. import matplotlib.pyplot as plt
  3. from matplotlib.testing.decorators import image_comparison
  4. from matplotlib.testing.widgets import do_event, get_ax
  5. from numpy.testing import assert_allclose
  6. import pytest
  7. def check_rectangle(**kwargs):
  8. ax = get_ax()
  9. def onselect(epress, erelease):
  10. ax._got_onselect = True
  11. assert epress.xdata == 100
  12. assert epress.ydata == 100
  13. assert erelease.xdata == 199
  14. assert erelease.ydata == 199
  15. tool = widgets.RectangleSelector(ax, onselect, **kwargs)
  16. do_event(tool, 'press', xdata=100, ydata=100, button=1)
  17. do_event(tool, 'onmove', xdata=199, ydata=199, button=1)
  18. # purposely drag outside of axis for release
  19. do_event(tool, 'release', xdata=250, ydata=250, button=1)
  20. if kwargs.get('drawtype', None) not in ['line', 'none']:
  21. assert_allclose(tool.geometry,
  22. [[100., 100, 199, 199, 100],
  23. [100, 199, 199, 100, 100]],
  24. err_msg=tool.geometry)
  25. assert ax._got_onselect
  26. def test_rectangle_selector():
  27. check_rectangle()
  28. check_rectangle(drawtype='line', useblit=False)
  29. check_rectangle(useblit=True, button=1)
  30. check_rectangle(drawtype='none', minspanx=10, minspany=10)
  31. check_rectangle(minspanx=10, minspany=10, spancoords='pixels')
  32. check_rectangle(rectprops=dict(fill=True))
  33. def test_ellipse():
  34. """For ellipse, test out the key modifiers"""
  35. ax = get_ax()
  36. def onselect(epress, erelease):
  37. pass
  38. tool = widgets.EllipseSelector(ax, onselect=onselect,
  39. maxdist=10, interactive=True)
  40. tool.extents = (100, 150, 100, 150)
  41. # drag the rectangle
  42. do_event(tool, 'press', xdata=10, ydata=10, button=1,
  43. key=' ')
  44. do_event(tool, 'onmove', xdata=30, ydata=30, button=1)
  45. do_event(tool, 'release', xdata=30, ydata=30, button=1)
  46. assert tool.extents == (120, 170, 120, 170)
  47. # create from center
  48. do_event(tool, 'on_key_press', xdata=100, ydata=100, button=1,
  49. key='control')
  50. do_event(tool, 'press', xdata=100, ydata=100, button=1)
  51. do_event(tool, 'onmove', xdata=125, ydata=125, button=1)
  52. do_event(tool, 'release', xdata=125, ydata=125, button=1)
  53. do_event(tool, 'on_key_release', xdata=100, ydata=100, button=1,
  54. key='control')
  55. assert tool.extents == (75, 125, 75, 125)
  56. # create a square
  57. do_event(tool, 'on_key_press', xdata=10, ydata=10, button=1,
  58. key='shift')
  59. do_event(tool, 'press', xdata=10, ydata=10, button=1)
  60. do_event(tool, 'onmove', xdata=35, ydata=30, button=1)
  61. do_event(tool, 'release', xdata=35, ydata=30, button=1)
  62. do_event(tool, 'on_key_release', xdata=10, ydata=10, button=1,
  63. key='shift')
  64. extents = [int(e) for e in tool.extents]
  65. assert extents == [10, 35, 10, 34]
  66. # create a square from center
  67. do_event(tool, 'on_key_press', xdata=100, ydata=100, button=1,
  68. key='ctrl+shift')
  69. do_event(tool, 'press', xdata=100, ydata=100, button=1)
  70. do_event(tool, 'onmove', xdata=125, ydata=130, button=1)
  71. do_event(tool, 'release', xdata=125, ydata=130, button=1)
  72. do_event(tool, 'on_key_release', xdata=100, ydata=100, button=1,
  73. key='ctrl+shift')
  74. extents = [int(e) for e in tool.extents]
  75. assert extents == [70, 129, 70, 130]
  76. assert tool.geometry.shape == (2, 73)
  77. assert_allclose(tool.geometry[:, 0], [70., 100])
  78. def test_rectangle_handles():
  79. ax = get_ax()
  80. def onselect(epress, erelease):
  81. pass
  82. tool = widgets.RectangleSelector(ax, onselect=onselect,
  83. maxdist=10, interactive=True)
  84. tool.extents = (100, 150, 100, 150)
  85. assert tool.corners == (
  86. (100, 150, 150, 100), (100, 100, 150, 150))
  87. assert tool.extents == (100, 150, 100, 150)
  88. assert tool.edge_centers == (
  89. (100, 125.0, 150, 125.0), (125.0, 100, 125.0, 150))
  90. assert tool.extents == (100, 150, 100, 150)
  91. # grab a corner and move it
  92. do_event(tool, 'press', xdata=100, ydata=100)
  93. do_event(tool, 'onmove', xdata=120, ydata=120)
  94. do_event(tool, 'release', xdata=120, ydata=120)
  95. assert tool.extents == (120, 150, 120, 150)
  96. # grab the center and move it
  97. do_event(tool, 'press', xdata=132, ydata=132)
  98. do_event(tool, 'onmove', xdata=120, ydata=120)
  99. do_event(tool, 'release', xdata=120, ydata=120)
  100. assert tool.extents == (108, 138, 108, 138)
  101. # create a new rectangle
  102. do_event(tool, 'press', xdata=10, ydata=10)
  103. do_event(tool, 'onmove', xdata=100, ydata=100)
  104. do_event(tool, 'release', xdata=100, ydata=100)
  105. assert tool.extents == (10, 100, 10, 100)
  106. def check_span(*args, **kwargs):
  107. ax = get_ax()
  108. def onselect(vmin, vmax):
  109. ax._got_onselect = True
  110. assert vmin == 100
  111. assert vmax == 150
  112. def onmove(vmin, vmax):
  113. assert vmin == 100
  114. assert vmax == 125
  115. ax._got_on_move = True
  116. if 'onmove_callback' in kwargs:
  117. kwargs['onmove_callback'] = onmove
  118. tool = widgets.SpanSelector(ax, onselect, *args, **kwargs)
  119. do_event(tool, 'press', xdata=100, ydata=100, button=1)
  120. do_event(tool, 'onmove', xdata=125, ydata=125, button=1)
  121. do_event(tool, 'release', xdata=150, ydata=150, button=1)
  122. assert ax._got_onselect
  123. if 'onmove_callback' in kwargs:
  124. assert ax._got_on_move
  125. def test_span_selector():
  126. check_span('horizontal', minspan=10, useblit=True)
  127. check_span('vertical', onmove_callback=True, button=1)
  128. check_span('horizontal', rectprops=dict(fill=True))
  129. def check_lasso_selector(**kwargs):
  130. ax = get_ax()
  131. def onselect(verts):
  132. ax._got_onselect = True
  133. assert verts == [(100, 100), (125, 125), (150, 150)]
  134. tool = widgets.LassoSelector(ax, onselect, **kwargs)
  135. do_event(tool, 'press', xdata=100, ydata=100, button=1)
  136. do_event(tool, 'onmove', xdata=125, ydata=125, button=1)
  137. do_event(tool, 'release', xdata=150, ydata=150, button=1)
  138. assert ax._got_onselect
  139. def test_lasso_selector():
  140. check_lasso_selector()
  141. check_lasso_selector(useblit=False, lineprops=dict(color='red'))
  142. check_lasso_selector(useblit=True, button=1)
  143. def test_CheckButtons():
  144. ax = get_ax()
  145. check = widgets.CheckButtons(ax, ('a', 'b', 'c'), (True, False, True))
  146. assert check.get_status() == [True, False, True]
  147. check.set_active(0)
  148. assert check.get_status() == [False, False, True]
  149. cid = check.on_clicked(lambda: None)
  150. check.disconnect(cid)
  151. @image_comparison(['check_radio_buttons.png'], style='mpl20', remove_text=True)
  152. def test_check_radio_buttons_image():
  153. # Remove this line when this test image is regenerated.
  154. plt.rcParams['text.kerning_factor'] = 6
  155. get_ax()
  156. plt.subplots_adjust(left=0.3)
  157. rax1 = plt.axes([0.05, 0.7, 0.15, 0.15])
  158. rax2 = plt.axes([0.05, 0.2, 0.15, 0.15])
  159. widgets.RadioButtons(rax1, ('Radio 1', 'Radio 2', 'Radio 3'))
  160. widgets.CheckButtons(rax2, ('Check 1', 'Check 2', 'Check 3'),
  161. (False, True, True))
  162. @image_comparison(['check_bunch_of_radio_buttons.png'],
  163. style='mpl20', remove_text=True)
  164. def test_check_bunch_of_radio_buttons():
  165. rax = plt.axes([0.05, 0.1, 0.15, 0.7])
  166. widgets.RadioButtons(rax, ('B1', 'B2', 'B3', 'B4', 'B5', 'B6',
  167. 'B7', 'B8', 'B9', 'B10', 'B11', 'B12',
  168. 'B13', 'B14', 'B15'))
  169. def test_slider_slidermin_slidermax_invalid():
  170. fig, ax = plt.subplots()
  171. # test min/max with floats
  172. with pytest.raises(ValueError):
  173. widgets.Slider(ax=ax, label='', valmin=0.0, valmax=24.0,
  174. slidermin=10.0)
  175. with pytest.raises(ValueError):
  176. widgets.Slider(ax=ax, label='', valmin=0.0, valmax=24.0,
  177. slidermax=10.0)
  178. def test_slider_slidermin_slidermax():
  179. fig, ax = plt.subplots()
  180. slider_ = widgets.Slider(ax=ax, label='', valmin=0.0, valmax=24.0,
  181. valinit=5.0)
  182. slider = widgets.Slider(ax=ax, label='', valmin=0.0, valmax=24.0,
  183. valinit=1.0, slidermin=slider_)
  184. assert slider.val == slider_.val
  185. slider = widgets.Slider(ax=ax, label='', valmin=0.0, valmax=24.0,
  186. valinit=10.0, slidermax=slider_)
  187. assert slider.val == slider_.val
  188. def test_slider_valmin_valmax():
  189. fig, ax = plt.subplots()
  190. slider = widgets.Slider(ax=ax, label='', valmin=0.0, valmax=24.0,
  191. valinit=-10.0)
  192. assert slider.val == slider.valmin
  193. slider = widgets.Slider(ax=ax, label='', valmin=0.0, valmax=24.0,
  194. valinit=25.0)
  195. assert slider.val == slider.valmax
  196. def test_slider_horizontal_vertical():
  197. fig, ax = plt.subplots()
  198. slider = widgets.Slider(ax=ax, label='', valmin=0, valmax=24,
  199. valinit=12, orientation='horizontal')
  200. slider.set_val(10)
  201. assert slider.val == 10
  202. # check the dimension of the slider patch in axes units
  203. box = slider.poly.get_extents().transformed(ax.transAxes.inverted())
  204. assert_allclose(box.bounds, [0, 0, 10/24, 1])
  205. fig, ax = plt.subplots()
  206. slider = widgets.Slider(ax=ax, label='', valmin=0, valmax=24,
  207. valinit=12, orientation='vertical')
  208. slider.set_val(10)
  209. assert slider.val == 10
  210. # check the dimension of the slider patch in axes units
  211. box = slider.poly.get_extents().transformed(ax.transAxes.inverted())
  212. assert_allclose(box.bounds, [0, 0, 1, 10/24])
  213. def check_polygon_selector(event_sequence, expected_result, selections_count):
  214. """
  215. Helper function to test Polygon Selector.
  216. Parameters
  217. ----------
  218. event_sequence : list of tuples (etype, dict())
  219. A sequence of events to perform. The sequence is a list of tuples
  220. where the first element of the tuple is an etype (e.g., 'onmove',
  221. 'press', etc.), and the second element of the tuple is a dictionary of
  222. the arguments for the event (e.g., xdata=5, key='shift', etc.).
  223. expected_result : list of vertices (xdata, ydata)
  224. The list of vertices that are expected to result from the event
  225. sequence.
  226. selections_count : int
  227. Wait for the tool to call its `onselect` function `selections_count`
  228. times, before comparing the result to the `expected_result`
  229. """
  230. ax = get_ax()
  231. ax._selections_count = 0
  232. def onselect(vertices):
  233. ax._selections_count += 1
  234. ax._current_result = vertices
  235. tool = widgets.PolygonSelector(ax, onselect)
  236. for (etype, event_args) in event_sequence:
  237. do_event(tool, etype, **event_args)
  238. assert ax._selections_count == selections_count
  239. assert ax._current_result == expected_result
  240. def polygon_place_vertex(xdata, ydata):
  241. return [('onmove', dict(xdata=xdata, ydata=ydata)),
  242. ('press', dict(xdata=xdata, ydata=ydata)),
  243. ('release', dict(xdata=xdata, ydata=ydata))]
  244. def test_polygon_selector():
  245. # Simple polygon
  246. expected_result = [(50, 50), (150, 50), (50, 150)]
  247. event_sequence = (polygon_place_vertex(50, 50)
  248. + polygon_place_vertex(150, 50)
  249. + polygon_place_vertex(50, 150)
  250. + polygon_place_vertex(50, 50))
  251. check_polygon_selector(event_sequence, expected_result, 1)
  252. # Move first vertex before completing the polygon.
  253. expected_result = [(75, 50), (150, 50), (50, 150)]
  254. event_sequence = (polygon_place_vertex(50, 50)
  255. + polygon_place_vertex(150, 50)
  256. + [('on_key_press', dict(key='control')),
  257. ('onmove', dict(xdata=50, ydata=50)),
  258. ('press', dict(xdata=50, ydata=50)),
  259. ('onmove', dict(xdata=75, ydata=50)),
  260. ('release', dict(xdata=75, ydata=50)),
  261. ('on_key_release', dict(key='control'))]
  262. + polygon_place_vertex(50, 150)
  263. + polygon_place_vertex(75, 50))
  264. check_polygon_selector(event_sequence, expected_result, 1)
  265. # Move first two vertices at once before completing the polygon.
  266. expected_result = [(50, 75), (150, 75), (50, 150)]
  267. event_sequence = (polygon_place_vertex(50, 50)
  268. + polygon_place_vertex(150, 50)
  269. + [('on_key_press', dict(key='shift')),
  270. ('onmove', dict(xdata=100, ydata=100)),
  271. ('press', dict(xdata=100, ydata=100)),
  272. ('onmove', dict(xdata=100, ydata=125)),
  273. ('release', dict(xdata=100, ydata=125)),
  274. ('on_key_release', dict(key='shift'))]
  275. + polygon_place_vertex(50, 150)
  276. + polygon_place_vertex(50, 75))
  277. check_polygon_selector(event_sequence, expected_result, 1)
  278. # Move first vertex after completing the polygon.
  279. expected_result = [(75, 50), (150, 50), (50, 150)]
  280. event_sequence = (polygon_place_vertex(50, 50)
  281. + polygon_place_vertex(150, 50)
  282. + polygon_place_vertex(50, 150)
  283. + polygon_place_vertex(50, 50)
  284. + [('onmove', dict(xdata=50, ydata=50)),
  285. ('press', dict(xdata=50, ydata=50)),
  286. ('onmove', dict(xdata=75, ydata=50)),
  287. ('release', dict(xdata=75, ydata=50))])
  288. check_polygon_selector(event_sequence, expected_result, 2)
  289. # Move all vertices after completing the polygon.
  290. expected_result = [(75, 75), (175, 75), (75, 175)]
  291. event_sequence = (polygon_place_vertex(50, 50)
  292. + polygon_place_vertex(150, 50)
  293. + polygon_place_vertex(50, 150)
  294. + polygon_place_vertex(50, 50)
  295. + [('on_key_press', dict(key='shift')),
  296. ('onmove', dict(xdata=100, ydata=100)),
  297. ('press', dict(xdata=100, ydata=100)),
  298. ('onmove', dict(xdata=125, ydata=125)),
  299. ('release', dict(xdata=125, ydata=125)),
  300. ('on_key_release', dict(key='shift'))])
  301. check_polygon_selector(event_sequence, expected_result, 2)
  302. # Try to move a vertex and move all before placing any vertices.
  303. expected_result = [(50, 50), (150, 50), (50, 150)]
  304. event_sequence = ([('on_key_press', dict(key='control')),
  305. ('onmove', dict(xdata=100, ydata=100)),
  306. ('press', dict(xdata=100, ydata=100)),
  307. ('onmove', dict(xdata=125, ydata=125)),
  308. ('release', dict(xdata=125, ydata=125)),
  309. ('on_key_release', dict(key='control')),
  310. ('on_key_press', dict(key='shift')),
  311. ('onmove', dict(xdata=100, ydata=100)),
  312. ('press', dict(xdata=100, ydata=100)),
  313. ('onmove', dict(xdata=125, ydata=125)),
  314. ('release', dict(xdata=125, ydata=125)),
  315. ('on_key_release', dict(key='shift'))]
  316. + polygon_place_vertex(50, 50)
  317. + polygon_place_vertex(150, 50)
  318. + polygon_place_vertex(50, 150)
  319. + polygon_place_vertex(50, 50))
  320. check_polygon_selector(event_sequence, expected_result, 1)
  321. # Try to place vertex out-of-bounds, then reset, and start a new polygon.
  322. expected_result = [(50, 50), (150, 50), (50, 150)]
  323. event_sequence = (polygon_place_vertex(50, 50)
  324. + polygon_place_vertex(250, 50)
  325. + [('on_key_press', dict(key='escape')),
  326. ('on_key_release', dict(key='escape'))]
  327. + polygon_place_vertex(50, 50)
  328. + polygon_place_vertex(150, 50)
  329. + polygon_place_vertex(50, 150)
  330. + polygon_place_vertex(50, 50))
  331. check_polygon_selector(event_sequence, expected_result, 1)