test_offsetbox.py 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  1. from collections import namedtuple
  2. import numpy.testing as nptest
  3. import pytest
  4. from matplotlib.testing.decorators import image_comparison
  5. import matplotlib.pyplot as plt
  6. import matplotlib.patches as mpatches
  7. import matplotlib.lines as mlines
  8. from matplotlib.offsetbox import (
  9. AnchoredOffsetbox, DrawingArea, _get_packed_offsets)
  10. @image_comparison(['offsetbox_clipping'], remove_text=True)
  11. def test_offsetbox_clipping():
  12. # - create a plot
  13. # - put an AnchoredOffsetbox with a child DrawingArea
  14. # at the center of the axes
  15. # - give the DrawingArea a gray background
  16. # - put a black line across the bounds of the DrawingArea
  17. # - see that the black line is clipped to the edges of
  18. # the DrawingArea.
  19. fig, ax = plt.subplots()
  20. size = 100
  21. da = DrawingArea(size, size, clip=True)
  22. bg = mpatches.Rectangle((0, 0), size, size,
  23. facecolor='#CCCCCC',
  24. edgecolor='None',
  25. linewidth=0)
  26. line = mlines.Line2D([-size*.5, size*1.5], [size/2, size/2],
  27. color='black',
  28. linewidth=10)
  29. anchored_box = AnchoredOffsetbox(
  30. loc='center',
  31. child=da,
  32. pad=0.,
  33. frameon=False,
  34. bbox_to_anchor=(.5, .5),
  35. bbox_transform=ax.transAxes,
  36. borderpad=0.)
  37. da.add_artist(bg)
  38. da.add_artist(line)
  39. ax.add_artist(anchored_box)
  40. ax.set_xlim((0, 1))
  41. ax.set_ylim((0, 1))
  42. def test_offsetbox_clip_children():
  43. # - create a plot
  44. # - put an AnchoredOffsetbox with a child DrawingArea
  45. # at the center of the axes
  46. # - give the DrawingArea a gray background
  47. # - put a black line across the bounds of the DrawingArea
  48. # - see that the black line is clipped to the edges of
  49. # the DrawingArea.
  50. fig, ax = plt.subplots()
  51. size = 100
  52. da = DrawingArea(size, size, clip=True)
  53. bg = mpatches.Rectangle((0, 0), size, size,
  54. facecolor='#CCCCCC',
  55. edgecolor='None',
  56. linewidth=0)
  57. line = mlines.Line2D([-size*.5, size*1.5], [size/2, size/2],
  58. color='black',
  59. linewidth=10)
  60. anchored_box = AnchoredOffsetbox(
  61. loc='center',
  62. child=da,
  63. pad=0.,
  64. frameon=False,
  65. bbox_to_anchor=(.5, .5),
  66. bbox_transform=ax.transAxes,
  67. borderpad=0.)
  68. da.add_artist(bg)
  69. da.add_artist(line)
  70. ax.add_artist(anchored_box)
  71. fig.canvas.draw()
  72. assert not fig.stale
  73. da.clip_children = True
  74. assert fig.stale
  75. def test_offsetbox_loc_codes():
  76. # Check that valid string location codes all work with an AnchoredOffsetbox
  77. codes = {'upper right': 1,
  78. 'upper left': 2,
  79. 'lower left': 3,
  80. 'lower right': 4,
  81. 'right': 5,
  82. 'center left': 6,
  83. 'center right': 7,
  84. 'lower center': 8,
  85. 'upper center': 9,
  86. 'center': 10,
  87. }
  88. fig, ax = plt.subplots()
  89. da = DrawingArea(100, 100)
  90. for code in codes:
  91. anchored_box = AnchoredOffsetbox(loc=code, child=da)
  92. ax.add_artist(anchored_box)
  93. fig.canvas.draw()
  94. def test_expand_with_tight_layout():
  95. # Check issue reported in #10476, and updated due to #10784
  96. fig, ax = plt.subplots()
  97. d1 = [1, 2]
  98. d2 = [2, 1]
  99. ax.plot(d1, label='series 1')
  100. ax.plot(d2, label='series 2')
  101. ax.legend(ncol=2, mode='expand')
  102. fig.tight_layout() # where the crash used to happen
  103. @pytest.mark.parametrize('wd_list',
  104. ([(150, 1)], [(150, 1)]*3, [(0.1, 1)], [(0.1, 1)]*2))
  105. @pytest.mark.parametrize('total', (250, 100, 0, -1, None))
  106. @pytest.mark.parametrize('sep', (250, 1, 0, -1))
  107. @pytest.mark.parametrize('mode', ("expand", "fixed", "equal"))
  108. def test_get_packed_offsets(wd_list, total, sep, mode):
  109. # Check a (rather arbitrary) set of parameters due to successive similar
  110. # issue tickets (at least #10476 and #10784) related to corner cases
  111. # triggered inside this function when calling higher-level functions
  112. # (e.g. `Axes.legend`).
  113. # These are just some additional smoke tests. The output is untested.
  114. _get_packed_offsets(wd_list, total, sep, mode=mode)
  115. _Params = namedtuple('_params', 'wd_list, total, sep, expected')
  116. @pytest.mark.parametrize('wd_list, total, sep, expected', [
  117. _Params( # total=None
  118. [(3, 0), (1, 0), (2, 0)], total=None, sep=1, expected=(8, [0, 4, 6])),
  119. _Params( # total larger than required
  120. [(3, 0), (1, 0), (2, 0)], total=10, sep=1, expected=(10, [0, 4, 6])),
  121. _Params( # total smaller than required
  122. [(3, 0), (1, 0), (2, 0)], total=5, sep=1, expected=(5, [0, 4, 6])),
  123. ])
  124. def test_get_packed_offsets_fixed(wd_list, total, sep, expected):
  125. result = _get_packed_offsets(wd_list, total, sep, mode='fixed')
  126. assert result[0] == expected[0]
  127. nptest.assert_allclose(result[1], expected[1])
  128. @pytest.mark.parametrize('wd_list, total, sep, expected', [
  129. _Params( # total=None (implicit 1)
  130. [(.1, 0)] * 3, total=None, sep=None, expected=(1, [0, .45, .9])),
  131. _Params( # total larger than sum of widths
  132. [(3, 0), (1, 0), (2, 0)], total=10, sep=1, expected=(10, [0, 5, 8])),
  133. _Params( # total smaller sum of widths: overlapping boxes
  134. [(3, 0), (1, 0), (2, 0)], total=5, sep=1, expected=(5, [0, 2.5, 3])),
  135. ])
  136. def test_get_packed_offsets_expand(wd_list, total, sep, expected):
  137. result = _get_packed_offsets(wd_list, total, sep, mode='expand')
  138. assert result[0] == expected[0]
  139. nptest.assert_allclose(result[1], expected[1])
  140. @pytest.mark.parametrize('wd_list, total, sep, expected', [
  141. _Params( # total larger than required
  142. [(3, 0), (2, 0), (1, 0)], total=6, sep=None, expected=(6, [0, 2, 4])),
  143. _Params( # total smaller sum of widths: overlapping boxes
  144. [(3, 0), (2, 0), (1, 0), (.5, 0)], total=2, sep=None,
  145. expected=(2, [0, 0.5, 1, 1.5])),
  146. _Params( # total larger than required
  147. [(.5, 0), (1, 0), (.2, 0)], total=None, sep=1,
  148. expected=(6, [0, 2, 4])),
  149. # the case total=None, sep=None is tested separately below
  150. ])
  151. def test_get_packed_offsets_equal(wd_list, total, sep, expected):
  152. result = _get_packed_offsets(wd_list, total, sep, mode='equal')
  153. assert result[0] == expected[0]
  154. nptest.assert_allclose(result[1], expected[1])
  155. def test_get_packed_offsets_equal_total_none_sep_none():
  156. with pytest.raises(ValueError):
  157. _get_packed_offsets([(1, 0)] * 3, total=None, sep=None, mode='equal')