test_legend.py 48 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343
  1. import collections
  2. import platform
  3. from unittest import mock
  4. import warnings
  5. import numpy as np
  6. from numpy.testing import assert_allclose
  7. import pytest
  8. from matplotlib.testing.decorators import check_figures_equal, image_comparison
  9. from matplotlib.testing._markers import needs_usetex
  10. import matplotlib.pyplot as plt
  11. import matplotlib as mpl
  12. import matplotlib.patches as mpatches
  13. import matplotlib.transforms as mtransforms
  14. import matplotlib.collections as mcollections
  15. import matplotlib.lines as mlines
  16. from matplotlib.legend_handler import HandlerTuple
  17. import matplotlib.legend as mlegend
  18. from matplotlib import _api, rc_context
  19. from matplotlib.font_manager import FontProperties
  20. def test_legend_ordereddict():
  21. # smoketest that ordereddict inputs work...
  22. X = np.random.randn(10)
  23. Y = np.random.randn(10)
  24. labels = ['a'] * 5 + ['b'] * 5
  25. colors = ['r'] * 5 + ['g'] * 5
  26. fig, ax = plt.subplots()
  27. for x, y, label, color in zip(X, Y, labels, colors):
  28. ax.scatter(x, y, label=label, c=color)
  29. handles, labels = ax.get_legend_handles_labels()
  30. legend = collections.OrderedDict(zip(labels, handles))
  31. ax.legend(legend.values(), legend.keys(),
  32. loc='center left', bbox_to_anchor=(1, .5))
  33. @image_comparison(['legend_auto1'], remove_text=True)
  34. def test_legend_auto1():
  35. """Test automatic legend placement"""
  36. fig, ax = plt.subplots()
  37. x = np.arange(100)
  38. ax.plot(x, 50 - x, 'o', label='y=1')
  39. ax.plot(x, x - 50, 'o', label='y=-1')
  40. ax.legend(loc='best')
  41. @image_comparison(['legend_auto2'], remove_text=True)
  42. def test_legend_auto2():
  43. """Test automatic legend placement"""
  44. fig, ax = plt.subplots()
  45. x = np.arange(100)
  46. b1 = ax.bar(x, x, align='edge', color='m')
  47. b2 = ax.bar(x, x[::-1], align='edge', color='g')
  48. ax.legend([b1[0], b2[0]], ['up', 'down'], loc='best')
  49. @image_comparison(['legend_auto3'])
  50. def test_legend_auto3():
  51. """Test automatic legend placement"""
  52. fig, ax = plt.subplots()
  53. x = [0.9, 0.1, 0.1, 0.9, 0.9, 0.5]
  54. y = [0.95, 0.95, 0.05, 0.05, 0.5, 0.5]
  55. ax.plot(x, y, 'o-', label='line')
  56. ax.set_xlim(0.0, 1.0)
  57. ax.set_ylim(0.0, 1.0)
  58. ax.legend(loc='best')
  59. def test_legend_auto4():
  60. """
  61. Check that the legend location with automatic placement is the same,
  62. whatever the histogram type is. Related to issue #9580.
  63. """
  64. # NB: barstacked is pointless with a single dataset.
  65. fig, axs = plt.subplots(ncols=3, figsize=(6.4, 2.4))
  66. leg_bboxes = []
  67. for ax, ht in zip(axs.flat, ('bar', 'step', 'stepfilled')):
  68. ax.set_title(ht)
  69. # A high bar on the left but an even higher one on the right.
  70. ax.hist([0] + 5*[9], bins=range(10), label="Legend", histtype=ht)
  71. leg = ax.legend(loc="best")
  72. fig.canvas.draw()
  73. leg_bboxes.append(
  74. leg.get_window_extent().transformed(ax.transAxes.inverted()))
  75. # The histogram type "bar" is assumed to be the correct reference.
  76. assert_allclose(leg_bboxes[1].bounds, leg_bboxes[0].bounds)
  77. assert_allclose(leg_bboxes[2].bounds, leg_bboxes[0].bounds)
  78. def test_legend_auto5():
  79. """
  80. Check that the automatic placement handle a rather complex
  81. case with non rectangular patch. Related to issue #9580.
  82. """
  83. fig, axs = plt.subplots(ncols=2, figsize=(9.6, 4.8))
  84. leg_bboxes = []
  85. for ax, loc in zip(axs.flat, ("center", "best")):
  86. # An Ellipse patch at the top, a U-shaped Polygon patch at the
  87. # bottom and a ring-like Wedge patch: the correct placement of
  88. # the legend should be in the center.
  89. for _patch in [
  90. mpatches.Ellipse(
  91. xy=(0.5, 0.9), width=0.8, height=0.2, fc="C1"),
  92. mpatches.Polygon(np.array([
  93. [0, 1], [0, 0], [1, 0], [1, 1], [0.9, 1.0], [0.9, 0.1],
  94. [0.1, 0.1], [0.1, 1.0], [0.1, 1.0]]), fc="C1"),
  95. mpatches.Wedge((0.5, 0.5), 0.5, 0, 360, width=0.05, fc="C0")
  96. ]:
  97. ax.add_patch(_patch)
  98. ax.plot([0.1, 0.9], [0.9, 0.9], label="A segment") # sthg to label
  99. leg = ax.legend(loc=loc)
  100. fig.canvas.draw()
  101. leg_bboxes.append(
  102. leg.get_window_extent().transformed(ax.transAxes.inverted()))
  103. assert_allclose(leg_bboxes[1].bounds, leg_bboxes[0].bounds)
  104. @image_comparison(['legend_various_labels'], remove_text=True)
  105. def test_various_labels():
  106. # tests all sorts of label types
  107. fig = plt.figure()
  108. ax = fig.add_subplot(121)
  109. ax.plot(np.arange(4), 'o', label=1)
  110. ax.plot(np.linspace(4, 4.1), 'o', label='Développés')
  111. ax.plot(np.arange(4, 1, -1), 'o', label='__nolegend__')
  112. ax.legend(numpoints=1, loc='best')
  113. def test_legend_label_with_leading_underscore():
  114. """
  115. Test that artists with labels starting with an underscore are not added to
  116. the legend, and that a warning is issued if one tries to add them
  117. explicitly.
  118. """
  119. fig, ax = plt.subplots()
  120. line, = ax.plot([0, 1], label='_foo')
  121. with pytest.warns(_api.MatplotlibDeprecationWarning, match="with an underscore"):
  122. legend = ax.legend(handles=[line])
  123. assert len(legend.legend_handles) == 0
  124. @image_comparison(['legend_labels_first.png'], remove_text=True)
  125. def test_labels_first():
  126. # test labels to left of markers
  127. fig, ax = plt.subplots()
  128. ax.plot(np.arange(10), '-o', label=1)
  129. ax.plot(np.ones(10)*5, ':x', label="x")
  130. ax.plot(np.arange(20, 10, -1), 'd', label="diamond")
  131. ax.legend(loc='best', markerfirst=False)
  132. @image_comparison(['legend_multiple_keys.png'], remove_text=True)
  133. def test_multiple_keys():
  134. # test legend entries with multiple keys
  135. fig, ax = plt.subplots()
  136. p1, = ax.plot([1, 2, 3], '-o')
  137. p2, = ax.plot([2, 3, 4], '-x')
  138. p3, = ax.plot([3, 4, 5], '-d')
  139. ax.legend([(p1, p2), (p2, p1), p3], ['two keys', 'pad=0', 'one key'],
  140. numpoints=1,
  141. handler_map={(p1, p2): HandlerTuple(ndivide=None),
  142. (p2, p1): HandlerTuple(ndivide=None, pad=0)})
  143. @image_comparison(['rgba_alpha.png'], remove_text=True,
  144. tol=0 if platform.machine() == 'x86_64' else 0.01)
  145. def test_alpha_rgba():
  146. fig, ax = plt.subplots()
  147. ax.plot(range(10), lw=5)
  148. leg = plt.legend(['Longlabel that will go away'], loc='center')
  149. leg.legendPatch.set_facecolor([1, 0, 0, 0.5])
  150. @image_comparison(['rcparam_alpha.png'], remove_text=True,
  151. tol=0 if platform.machine() == 'x86_64' else 0.01)
  152. def test_alpha_rcparam():
  153. fig, ax = plt.subplots()
  154. ax.plot(range(10), lw=5)
  155. with mpl.rc_context(rc={'legend.framealpha': .75}):
  156. leg = plt.legend(['Longlabel that will go away'], loc='center')
  157. # this alpha is going to be over-ridden by the rcparam with
  158. # sets the alpha of the patch to be non-None which causes the alpha
  159. # value of the face color to be discarded. This behavior may not be
  160. # ideal, but it is what it is and we should keep track of it changing
  161. leg.legendPatch.set_facecolor([1, 0, 0, 0.5])
  162. @image_comparison(['fancy'], remove_text=True, tol=0.05)
  163. def test_fancy():
  164. # Tolerance caused by changing default shadow "shade" from 0.3 to 1 - 0.7 =
  165. # 0.30000000000000004
  166. # using subplot triggers some offsetbox functionality untested elsewhere
  167. plt.subplot(121)
  168. plt.plot([5] * 10, 'o--', label='XX')
  169. plt.scatter(np.arange(10), np.arange(10, 0, -1), label='XX\nXX')
  170. plt.errorbar(np.arange(10), np.arange(10), xerr=0.5,
  171. yerr=0.5, label='XX')
  172. plt.legend(loc="center left", bbox_to_anchor=[1.0, 0.5],
  173. ncols=2, shadow=True, title="My legend", numpoints=1)
  174. @image_comparison(['framealpha'], remove_text=True,
  175. tol=0 if platform.machine() == 'x86_64' else 0.02)
  176. def test_framealpha():
  177. x = np.linspace(1, 100, 100)
  178. y = x
  179. plt.plot(x, y, label='mylabel', lw=10)
  180. plt.legend(framealpha=0.5)
  181. @image_comparison(['scatter_rc3', 'scatter_rc1'], remove_text=True)
  182. def test_rc():
  183. # using subplot triggers some offsetbox functionality untested elsewhere
  184. plt.figure()
  185. ax = plt.subplot(121)
  186. ax.scatter(np.arange(10), np.arange(10, 0, -1), label='three')
  187. ax.legend(loc="center left", bbox_to_anchor=[1.0, 0.5],
  188. title="My legend")
  189. mpl.rcParams['legend.scatterpoints'] = 1
  190. plt.figure()
  191. ax = plt.subplot(121)
  192. ax.scatter(np.arange(10), np.arange(10, 0, -1), label='one')
  193. ax.legend(loc="center left", bbox_to_anchor=[1.0, 0.5],
  194. title="My legend")
  195. @image_comparison(['legend_expand'], remove_text=True)
  196. def test_legend_expand():
  197. """Test expand mode"""
  198. legend_modes = [None, "expand"]
  199. fig, axs = plt.subplots(len(legend_modes), 1)
  200. x = np.arange(100)
  201. for ax, mode in zip(axs, legend_modes):
  202. ax.plot(x, 50 - x, 'o', label='y=1')
  203. l1 = ax.legend(loc='upper left', mode=mode)
  204. ax.add_artist(l1)
  205. ax.plot(x, x - 50, 'o', label='y=-1')
  206. l2 = ax.legend(loc='right', mode=mode)
  207. ax.add_artist(l2)
  208. ax.legend(loc='lower left', mode=mode, ncols=2)
  209. @image_comparison(['hatching'], remove_text=True, style='default')
  210. def test_hatching():
  211. # Remove legend texts when this image is regenerated.
  212. # Remove this line when this test image is regenerated.
  213. plt.rcParams['text.kerning_factor'] = 6
  214. fig, ax = plt.subplots()
  215. # Patches
  216. patch = plt.Rectangle((0, 0), 0.3, 0.3, hatch='xx',
  217. label='Patch\ndefault color\nfilled')
  218. ax.add_patch(patch)
  219. patch = plt.Rectangle((0.33, 0), 0.3, 0.3, hatch='||', edgecolor='C1',
  220. label='Patch\nexplicit color\nfilled')
  221. ax.add_patch(patch)
  222. patch = plt.Rectangle((0, 0.4), 0.3, 0.3, hatch='xx', fill=False,
  223. label='Patch\ndefault color\nunfilled')
  224. ax.add_patch(patch)
  225. patch = plt.Rectangle((0.33, 0.4), 0.3, 0.3, hatch='||', fill=False,
  226. edgecolor='C1',
  227. label='Patch\nexplicit color\nunfilled')
  228. ax.add_patch(patch)
  229. # Paths
  230. ax.fill_between([0, .15, .3], [.8, .8, .8], [.9, 1.0, .9],
  231. hatch='+', label='Path\ndefault color')
  232. ax.fill_between([.33, .48, .63], [.8, .8, .8], [.9, 1.0, .9],
  233. hatch='+', edgecolor='C2', label='Path\nexplicit color')
  234. ax.set_xlim(-0.01, 1.1)
  235. ax.set_ylim(-0.01, 1.1)
  236. ax.legend(handlelength=4, handleheight=4)
  237. def test_legend_remove():
  238. fig, ax = plt.subplots()
  239. lines = ax.plot(range(10))
  240. leg = fig.legend(lines, "test")
  241. leg.remove()
  242. assert fig.legends == []
  243. leg = ax.legend("test")
  244. leg.remove()
  245. assert ax.get_legend() is None
  246. def test_reverse_legend_handles_and_labels():
  247. """Check that the legend handles and labels are reversed."""
  248. fig, ax = plt.subplots()
  249. x = 1
  250. y = 1
  251. labels = ["First label", "Second label", "Third label"]
  252. markers = ['.', ',', 'o']
  253. ax.plot(x, y, markers[0], label=labels[0])
  254. ax.plot(x, y, markers[1], label=labels[1])
  255. ax.plot(x, y, markers[2], label=labels[2])
  256. leg = ax.legend(reverse=True)
  257. actual_labels = [t.get_text() for t in leg.get_texts()]
  258. actual_markers = [h.get_marker() for h in leg.legend_handles]
  259. assert actual_labels == list(reversed(labels))
  260. assert actual_markers == list(reversed(markers))
  261. @check_figures_equal(extensions=["png"])
  262. def test_reverse_legend_display(fig_test, fig_ref):
  263. """Check that the rendered legend entries are reversed"""
  264. ax = fig_test.subplots()
  265. ax.plot([1], 'ro', label="first")
  266. ax.plot([2], 'bx', label="second")
  267. ax.legend(reverse=True)
  268. ax = fig_ref.subplots()
  269. ax.plot([2], 'bx', label="second")
  270. ax.plot([1], 'ro', label="first")
  271. ax.legend()
  272. class TestLegendFunction:
  273. # Tests the legend function on the Axes and pyplot.
  274. def test_legend_no_args(self):
  275. lines = plt.plot(range(10), label='hello world')
  276. with mock.patch('matplotlib.legend.Legend') as Legend:
  277. plt.legend()
  278. Legend.assert_called_with(plt.gca(), lines, ['hello world'])
  279. def test_legend_positional_handles_labels(self):
  280. lines = plt.plot(range(10))
  281. with mock.patch('matplotlib.legend.Legend') as Legend:
  282. plt.legend(lines, ['hello world'])
  283. Legend.assert_called_with(plt.gca(), lines, ['hello world'])
  284. def test_legend_positional_handles_only(self):
  285. lines = plt.plot(range(10))
  286. with pytest.raises(TypeError, match='but found an Artist'):
  287. # a single arg is interpreted as labels
  288. # it's a common error to just pass handles
  289. plt.legend(lines)
  290. def test_legend_positional_labels_only(self):
  291. lines = plt.plot(range(10), label='hello world')
  292. with mock.patch('matplotlib.legend.Legend') as Legend:
  293. plt.legend(['foobar'])
  294. Legend.assert_called_with(plt.gca(), lines, ['foobar'])
  295. def test_legend_three_args(self):
  296. lines = plt.plot(range(10), label='hello world')
  297. with mock.patch('matplotlib.legend.Legend') as Legend:
  298. plt.legend(lines, ['foobar'], loc='right')
  299. Legend.assert_called_with(plt.gca(), lines, ['foobar'], loc='right')
  300. def test_legend_handler_map(self):
  301. lines = plt.plot(range(10), label='hello world')
  302. with mock.patch('matplotlib.legend.'
  303. '_get_legend_handles_labels') as handles_labels:
  304. handles_labels.return_value = lines, ['hello world']
  305. plt.legend(handler_map={'1': 2})
  306. handles_labels.assert_called_with([plt.gca()], {'1': 2})
  307. def test_legend_kwargs_handles_only(self):
  308. fig, ax = plt.subplots()
  309. x = np.linspace(0, 1, 11)
  310. ln1, = ax.plot(x, x, label='x')
  311. ln2, = ax.plot(x, 2*x, label='2x')
  312. ln3, = ax.plot(x, 3*x, label='3x')
  313. with mock.patch('matplotlib.legend.Legend') as Legend:
  314. ax.legend(handles=[ln3, ln2]) # reversed and not ln1
  315. Legend.assert_called_with(ax, [ln3, ln2], ['3x', '2x'])
  316. def test_legend_kwargs_labels_only(self):
  317. fig, ax = plt.subplots()
  318. x = np.linspace(0, 1, 11)
  319. ln1, = ax.plot(x, x)
  320. ln2, = ax.plot(x, 2*x)
  321. with mock.patch('matplotlib.legend.Legend') as Legend:
  322. ax.legend(labels=['x', '2x'])
  323. Legend.assert_called_with(ax, [ln1, ln2], ['x', '2x'])
  324. def test_legend_kwargs_handles_labels(self):
  325. fig, ax = plt.subplots()
  326. th = np.linspace(0, 2*np.pi, 1024)
  327. lns, = ax.plot(th, np.sin(th), label='sin')
  328. lnc, = ax.plot(th, np.cos(th), label='cos')
  329. with mock.patch('matplotlib.legend.Legend') as Legend:
  330. # labels of lns, lnc are overwritten with explicit ('a', 'b')
  331. ax.legend(labels=('a', 'b'), handles=(lnc, lns))
  332. Legend.assert_called_with(ax, (lnc, lns), ('a', 'b'))
  333. def test_warn_mixed_args_and_kwargs(self):
  334. fig, ax = plt.subplots()
  335. th = np.linspace(0, 2*np.pi, 1024)
  336. lns, = ax.plot(th, np.sin(th), label='sin')
  337. lnc, = ax.plot(th, np.cos(th), label='cos')
  338. with pytest.warns(UserWarning) as record:
  339. ax.legend((lnc, lns), labels=('a', 'b'))
  340. assert len(record) == 1
  341. assert str(record[0].message) == (
  342. "You have mixed positional and keyword arguments, some input may "
  343. "be discarded.")
  344. def test_parasite(self):
  345. from mpl_toolkits.axes_grid1 import host_subplot # type: ignore
  346. host = host_subplot(111)
  347. par = host.twinx()
  348. p1, = host.plot([0, 1, 2], [0, 1, 2], label="Density")
  349. p2, = par.plot([0, 1, 2], [0, 3, 2], label="Temperature")
  350. with mock.patch('matplotlib.legend.Legend') as Legend:
  351. plt.legend()
  352. Legend.assert_called_with(host, [p1, p2], ['Density', 'Temperature'])
  353. class TestLegendFigureFunction:
  354. # Tests the legend function for figure
  355. def test_legend_handle_label(self):
  356. fig, ax = plt.subplots()
  357. lines = ax.plot(range(10))
  358. with mock.patch('matplotlib.legend.Legend') as Legend:
  359. fig.legend(lines, ['hello world'])
  360. Legend.assert_called_with(fig, lines, ['hello world'],
  361. bbox_transform=fig.transFigure)
  362. def test_legend_no_args(self):
  363. fig, ax = plt.subplots()
  364. lines = ax.plot(range(10), label='hello world')
  365. with mock.patch('matplotlib.legend.Legend') as Legend:
  366. fig.legend()
  367. Legend.assert_called_with(fig, lines, ['hello world'],
  368. bbox_transform=fig.transFigure)
  369. def test_legend_label_arg(self):
  370. fig, ax = plt.subplots()
  371. lines = ax.plot(range(10))
  372. with mock.patch('matplotlib.legend.Legend') as Legend:
  373. fig.legend(['foobar'])
  374. Legend.assert_called_with(fig, lines, ['foobar'],
  375. bbox_transform=fig.transFigure)
  376. def test_legend_label_three_args(self):
  377. fig, ax = plt.subplots()
  378. lines = ax.plot(range(10))
  379. with pytest.raises(TypeError, match="0-2"):
  380. fig.legend(lines, ['foobar'], 'right')
  381. with pytest.raises(TypeError, match="0-2"):
  382. fig.legend(lines, ['foobar'], 'right', loc='left')
  383. def test_legend_kw_args(self):
  384. fig, axs = plt.subplots(1, 2)
  385. lines = axs[0].plot(range(10))
  386. lines2 = axs[1].plot(np.arange(10) * 2.)
  387. with mock.patch('matplotlib.legend.Legend') as Legend:
  388. fig.legend(loc='right', labels=('a', 'b'), handles=(lines, lines2))
  389. Legend.assert_called_with(
  390. fig, (lines, lines2), ('a', 'b'), loc='right',
  391. bbox_transform=fig.transFigure)
  392. def test_warn_args_kwargs(self):
  393. fig, axs = plt.subplots(1, 2)
  394. lines = axs[0].plot(range(10))
  395. lines2 = axs[1].plot(np.arange(10) * 2.)
  396. with pytest.warns(UserWarning) as record:
  397. fig.legend((lines, lines2), labels=('a', 'b'))
  398. assert len(record) == 1
  399. assert str(record[0].message) == (
  400. "You have mixed positional and keyword arguments, some input may "
  401. "be discarded.")
  402. def test_figure_legend_outside():
  403. todos = ['upper ' + pos for pos in ['left', 'center', 'right']]
  404. todos += ['lower ' + pos for pos in ['left', 'center', 'right']]
  405. todos += ['left ' + pos for pos in ['lower', 'center', 'upper']]
  406. todos += ['right ' + pos for pos in ['lower', 'center', 'upper']]
  407. upperext = [20.347556, 27.722556, 790.583, 545.499]
  408. lowerext = [20.347556, 71.056556, 790.583, 588.833]
  409. leftext = [151.681556, 27.722556, 790.583, 588.833]
  410. rightext = [20.347556, 27.722556, 659.249, 588.833]
  411. axbb = [upperext, upperext, upperext,
  412. lowerext, lowerext, lowerext,
  413. leftext, leftext, leftext,
  414. rightext, rightext, rightext]
  415. legbb = [[10., 555., 133., 590.], # upper left
  416. [338.5, 555., 461.5, 590.], # upper center
  417. [667, 555., 790., 590.], # upper right
  418. [10., 10., 133., 45.], # lower left
  419. [338.5, 10., 461.5, 45.], # lower center
  420. [667., 10., 790., 45.], # lower right
  421. [10., 10., 133., 45.], # left lower
  422. [10., 282.5, 133., 317.5], # left center
  423. [10., 555., 133., 590.], # left upper
  424. [667, 10., 790., 45.], # right lower
  425. [667., 282.5, 790., 317.5], # right center
  426. [667., 555., 790., 590.]] # right upper
  427. for nn, todo in enumerate(todos):
  428. print(todo)
  429. fig, axs = plt.subplots(constrained_layout=True, dpi=100)
  430. axs.plot(range(10), label='Boo1')
  431. leg = fig.legend(loc='outside ' + todo)
  432. fig.draw_without_rendering()
  433. assert_allclose(axs.get_window_extent().extents,
  434. axbb[nn])
  435. assert_allclose(leg.get_window_extent().extents,
  436. legbb[nn])
  437. @image_comparison(['legend_stackplot.png'])
  438. def test_legend_stackplot():
  439. """Test legend for PolyCollection using stackplot."""
  440. # related to #1341, #1943, and PR #3303
  441. fig, ax = plt.subplots()
  442. x = np.linspace(0, 10, 10)
  443. y1 = 1.0 * x
  444. y2 = 2.0 * x + 1
  445. y3 = 3.0 * x + 2
  446. ax.stackplot(x, y1, y2, y3, labels=['y1', 'y2', 'y3'])
  447. ax.set_xlim((0, 10))
  448. ax.set_ylim((0, 70))
  449. ax.legend(loc='best')
  450. def test_cross_figure_patch_legend():
  451. fig, ax = plt.subplots()
  452. fig2, ax2 = plt.subplots()
  453. brs = ax.bar(range(3), range(3))
  454. fig2.legend(brs, 'foo')
  455. def test_nanscatter():
  456. fig, ax = plt.subplots()
  457. h = ax.scatter([np.nan], [np.nan], marker="o",
  458. facecolor="r", edgecolor="r", s=3)
  459. ax.legend([h], ["scatter"])
  460. fig, ax = plt.subplots()
  461. for color in ['red', 'green', 'blue']:
  462. n = 750
  463. x, y = np.random.rand(2, n)
  464. scale = 200.0 * np.random.rand(n)
  465. ax.scatter(x, y, c=color, s=scale, label=color,
  466. alpha=0.3, edgecolors='none')
  467. ax.legend()
  468. ax.grid(True)
  469. def test_legend_repeatcheckok():
  470. fig, ax = plt.subplots()
  471. ax.scatter(0.0, 1.0, color='k', marker='o', label='test')
  472. ax.scatter(0.5, 0.0, color='r', marker='v', label='test')
  473. ax.legend()
  474. hand, lab = mlegend._get_legend_handles_labels([ax])
  475. assert len(lab) == 2
  476. fig, ax = plt.subplots()
  477. ax.scatter(0.0, 1.0, color='k', marker='o', label='test')
  478. ax.scatter(0.5, 0.0, color='k', marker='v', label='test')
  479. ax.legend()
  480. hand, lab = mlegend._get_legend_handles_labels([ax])
  481. assert len(lab) == 2
  482. @image_comparison(['not_covering_scatter.png'])
  483. def test_not_covering_scatter():
  484. colors = ['b', 'g', 'r']
  485. for n in range(3):
  486. plt.scatter([n], [n], color=colors[n])
  487. plt.legend(['foo', 'foo', 'foo'], loc='best')
  488. plt.gca().set_xlim(-0.5, 2.2)
  489. plt.gca().set_ylim(-0.5, 2.2)
  490. @image_comparison(['not_covering_scatter_transform.png'])
  491. def test_not_covering_scatter_transform():
  492. # Offsets point to top left, the default auto position
  493. offset = mtransforms.Affine2D().translate(-20, 20)
  494. x = np.linspace(0, 30, 1000)
  495. plt.plot(x, x)
  496. plt.scatter([20], [10], transform=offset + plt.gca().transData)
  497. plt.legend(['foo', 'bar'], loc='best')
  498. def test_linecollection_scaled_dashes():
  499. lines1 = [[(0, .5), (.5, 1)], [(.3, .6), (.2, .2)]]
  500. lines2 = [[[0.7, .2], [.8, .4]], [[.5, .7], [.6, .1]]]
  501. lines3 = [[[0.6, .2], [.8, .4]], [[.5, .7], [.1, .1]]]
  502. lc1 = mcollections.LineCollection(lines1, linestyles="--", lw=3)
  503. lc2 = mcollections.LineCollection(lines2, linestyles="-.")
  504. lc3 = mcollections.LineCollection(lines3, linestyles=":", lw=.5)
  505. fig, ax = plt.subplots()
  506. ax.add_collection(lc1)
  507. ax.add_collection(lc2)
  508. ax.add_collection(lc3)
  509. leg = ax.legend([lc1, lc2, lc3], ["line1", "line2", 'line 3'])
  510. h1, h2, h3 = leg.legend_handles
  511. for oh, lh in zip((lc1, lc2, lc3), (h1, h2, h3)):
  512. assert oh.get_linestyles()[0] == lh._dash_pattern
  513. def test_handler_numpoints():
  514. """Test legend handler with numpoints <= 1."""
  515. # related to #6921 and PR #8478
  516. fig, ax = plt.subplots()
  517. ax.plot(range(5), label='test')
  518. ax.legend(numpoints=0.5)
  519. def test_text_nohandler_warning():
  520. """Test that Text artists with labels raise a warning"""
  521. fig, ax = plt.subplots()
  522. ax.text(x=0, y=0, s="text", label="label")
  523. with pytest.warns(UserWarning) as record:
  524. ax.legend()
  525. assert len(record) == 1
  526. # this should _not_ warn:
  527. f, ax = plt.subplots()
  528. ax.pcolormesh(np.random.uniform(0, 1, (10, 10)))
  529. with warnings.catch_warnings():
  530. warnings.simplefilter("error")
  531. ax.get_legend_handles_labels()
  532. def test_empty_bar_chart_with_legend():
  533. """Test legend when bar chart is empty with a label."""
  534. # related to issue #13003. Calling plt.legend() should not
  535. # raise an IndexError.
  536. plt.bar([], [], label='test')
  537. plt.legend()
  538. @image_comparison(['shadow_argument_types.png'], remove_text=True,
  539. style='mpl20')
  540. def test_shadow_argument_types():
  541. # Test that different arguments for shadow work as expected
  542. fig, ax = plt.subplots()
  543. ax.plot([1, 2, 3], label='test')
  544. # Test various shadow configurations
  545. # as well as different ways of specifying colors
  546. legs = (ax.legend(loc='upper left', shadow=True), # True
  547. ax.legend(loc='upper right', shadow=False), # False
  548. ax.legend(loc='center left', # string
  549. shadow={'color': 'red', 'alpha': 0.1}),
  550. ax.legend(loc='center right', # tuple
  551. shadow={'color': (0.1, 0.2, 0.5), 'oy': -5}),
  552. ax.legend(loc='lower left', # tab
  553. shadow={'color': 'tab:cyan', 'ox': 10})
  554. )
  555. for l in legs:
  556. ax.add_artist(l)
  557. ax.legend(loc='lower right') # default
  558. def test_shadow_invalid_argument():
  559. # Test if invalid argument to legend shadow
  560. # (i.e. not [color|bool]) raises ValueError
  561. fig, ax = plt.subplots()
  562. ax.plot([1, 2, 3], label='test')
  563. with pytest.raises(ValueError, match="dict or bool"):
  564. ax.legend(loc="upper left", shadow="aardvark") # Bad argument
  565. def test_shadow_framealpha():
  566. # Test if framealpha is activated when shadow is True
  567. # and framealpha is not explicitly passed'''
  568. fig, ax = plt.subplots()
  569. ax.plot(range(100), label="test")
  570. leg = ax.legend(shadow=True, facecolor='w')
  571. assert leg.get_frame().get_alpha() == 1
  572. def test_legend_title_empty():
  573. # test that if we don't set the legend title, that
  574. # it comes back as an empty string, and that it is not
  575. # visible:
  576. fig, ax = plt.subplots()
  577. ax.plot(range(10))
  578. leg = ax.legend()
  579. assert leg.get_title().get_text() == ""
  580. assert not leg.get_title().get_visible()
  581. def test_legend_proper_window_extent():
  582. # test that legend returns the expected extent under various dpi...
  583. fig, ax = plt.subplots(dpi=100)
  584. ax.plot(range(10), label='Aardvark')
  585. leg = ax.legend()
  586. x01 = leg.get_window_extent(fig.canvas.get_renderer()).x0
  587. fig, ax = plt.subplots(dpi=200)
  588. ax.plot(range(10), label='Aardvark')
  589. leg = ax.legend()
  590. x02 = leg.get_window_extent(fig.canvas.get_renderer()).x0
  591. assert pytest.approx(x01*2, 0.1) == x02
  592. def test_window_extent_cached_renderer():
  593. fig, ax = plt.subplots(dpi=100)
  594. ax.plot(range(10), label='Aardvark')
  595. leg = ax.legend()
  596. leg2 = fig.legend()
  597. fig.canvas.draw()
  598. # check that get_window_extent will use the cached renderer
  599. leg.get_window_extent()
  600. leg2.get_window_extent()
  601. def test_legend_title_fontprop_fontsize():
  602. # test the title_fontsize kwarg
  603. plt.plot(range(10))
  604. with pytest.raises(ValueError):
  605. plt.legend(title='Aardvark', title_fontsize=22,
  606. title_fontproperties={'family': 'serif', 'size': 22})
  607. leg = plt.legend(title='Aardvark', title_fontproperties=FontProperties(
  608. family='serif', size=22))
  609. assert leg.get_title().get_size() == 22
  610. fig, axes = plt.subplots(2, 3, figsize=(10, 6))
  611. axes = axes.flat
  612. axes[0].plot(range(10))
  613. leg0 = axes[0].legend(title='Aardvark', title_fontsize=22)
  614. assert leg0.get_title().get_fontsize() == 22
  615. axes[1].plot(range(10))
  616. leg1 = axes[1].legend(title='Aardvark',
  617. title_fontproperties={'family': 'serif', 'size': 22})
  618. assert leg1.get_title().get_fontsize() == 22
  619. axes[2].plot(range(10))
  620. mpl.rcParams['legend.title_fontsize'] = None
  621. leg2 = axes[2].legend(title='Aardvark',
  622. title_fontproperties={'family': 'serif'})
  623. assert leg2.get_title().get_fontsize() == mpl.rcParams['font.size']
  624. axes[3].plot(range(10))
  625. leg3 = axes[3].legend(title='Aardvark')
  626. assert leg3.get_title().get_fontsize() == mpl.rcParams['font.size']
  627. axes[4].plot(range(10))
  628. mpl.rcParams['legend.title_fontsize'] = 20
  629. leg4 = axes[4].legend(title='Aardvark',
  630. title_fontproperties={'family': 'serif'})
  631. assert leg4.get_title().get_fontsize() == 20
  632. axes[5].plot(range(10))
  633. leg5 = axes[5].legend(title='Aardvark')
  634. assert leg5.get_title().get_fontsize() == 20
  635. @pytest.mark.parametrize('alignment', ('center', 'left', 'right'))
  636. def test_legend_alignment(alignment):
  637. fig, ax = plt.subplots()
  638. ax.plot(range(10), label='test')
  639. leg = ax.legend(title="Aardvark", alignment=alignment)
  640. assert leg.get_children()[0].align == alignment
  641. assert leg.get_alignment() == alignment
  642. @pytest.mark.parametrize('loc', ('center', 'best',))
  643. def test_ax_legend_set_loc(loc):
  644. fig, ax = plt.subplots()
  645. ax.plot(range(10), label='test')
  646. leg = ax.legend()
  647. leg.set_loc(loc)
  648. assert leg._get_loc() == mlegend.Legend.codes[loc]
  649. @pytest.mark.parametrize('loc', ('outside right', 'right',))
  650. def test_fig_legend_set_loc(loc):
  651. fig, ax = plt.subplots()
  652. ax.plot(range(10), label='test')
  653. leg = fig.legend()
  654. leg.set_loc(loc)
  655. loc = loc.split()[1] if loc.startswith("outside") else loc
  656. assert leg._get_loc() == mlegend.Legend.codes[loc]
  657. @pytest.mark.parametrize('alignment', ('center', 'left', 'right'))
  658. def test_legend_set_alignment(alignment):
  659. fig, ax = plt.subplots()
  660. ax.plot(range(10), label='test')
  661. leg = ax.legend()
  662. leg.set_alignment(alignment)
  663. assert leg.get_children()[0].align == alignment
  664. assert leg.get_alignment() == alignment
  665. @pytest.mark.parametrize('color', ('red', 'none', (.5, .5, .5)))
  666. def test_legend_labelcolor_single(color):
  667. # test labelcolor for a single color
  668. fig, ax = plt.subplots()
  669. ax.plot(np.arange(10), np.arange(10)*1, label='#1')
  670. ax.plot(np.arange(10), np.arange(10)*2, label='#2')
  671. ax.plot(np.arange(10), np.arange(10)*3, label='#3')
  672. leg = ax.legend(labelcolor=color)
  673. for text in leg.get_texts():
  674. assert mpl.colors.same_color(text.get_color(), color)
  675. def test_legend_labelcolor_list():
  676. # test labelcolor for a list of colors
  677. fig, ax = plt.subplots()
  678. ax.plot(np.arange(10), np.arange(10)*1, label='#1')
  679. ax.plot(np.arange(10), np.arange(10)*2, label='#2')
  680. ax.plot(np.arange(10), np.arange(10)*3, label='#3')
  681. leg = ax.legend(labelcolor=['r', 'g', 'b'])
  682. for text, color in zip(leg.get_texts(), ['r', 'g', 'b']):
  683. assert mpl.colors.same_color(text.get_color(), color)
  684. def test_legend_labelcolor_linecolor():
  685. # test the labelcolor for labelcolor='linecolor'
  686. fig, ax = plt.subplots()
  687. ax.plot(np.arange(10), np.arange(10)*1, label='#1', color='r')
  688. ax.plot(np.arange(10), np.arange(10)*2, label='#2', color='g')
  689. ax.plot(np.arange(10), np.arange(10)*3, label='#3', color='b')
  690. leg = ax.legend(labelcolor='linecolor')
  691. for text, color in zip(leg.get_texts(), ['r', 'g', 'b']):
  692. assert mpl.colors.same_color(text.get_color(), color)
  693. def test_legend_pathcollection_labelcolor_linecolor():
  694. # test the labelcolor for labelcolor='linecolor' on PathCollection
  695. fig, ax = plt.subplots()
  696. ax.scatter(np.arange(10), np.arange(10)*1, label='#1', c='r')
  697. ax.scatter(np.arange(10), np.arange(10)*2, label='#2', c='g')
  698. ax.scatter(np.arange(10), np.arange(10)*3, label='#3', c='b')
  699. leg = ax.legend(labelcolor='linecolor')
  700. for text, color in zip(leg.get_texts(), ['r', 'g', 'b']):
  701. assert mpl.colors.same_color(text.get_color(), color)
  702. def test_legend_pathcollection_labelcolor_linecolor_iterable():
  703. # test the labelcolor for labelcolor='linecolor' on PathCollection
  704. # with iterable colors
  705. fig, ax = plt.subplots()
  706. colors = np.random.default_rng().choice(['r', 'g', 'b'], 10)
  707. ax.scatter(np.arange(10), np.arange(10)*1, label='#1', c=colors)
  708. leg = ax.legend(labelcolor='linecolor')
  709. text, = leg.get_texts()
  710. assert mpl.colors.same_color(text.get_color(), 'black')
  711. def test_legend_pathcollection_labelcolor_linecolor_cmap():
  712. # test the labelcolor for labelcolor='linecolor' on PathCollection
  713. # with a colormap
  714. fig, ax = plt.subplots()
  715. ax.scatter(np.arange(10), np.arange(10), c=np.arange(10), label='#1')
  716. leg = ax.legend(labelcolor='linecolor')
  717. text, = leg.get_texts()
  718. assert mpl.colors.same_color(text.get_color(), 'black')
  719. def test_legend_labelcolor_markeredgecolor():
  720. # test the labelcolor for labelcolor='markeredgecolor'
  721. fig, ax = plt.subplots()
  722. ax.plot(np.arange(10), np.arange(10)*1, label='#1', markeredgecolor='r')
  723. ax.plot(np.arange(10), np.arange(10)*2, label='#2', markeredgecolor='g')
  724. ax.plot(np.arange(10), np.arange(10)*3, label='#3', markeredgecolor='b')
  725. leg = ax.legend(labelcolor='markeredgecolor')
  726. for text, color in zip(leg.get_texts(), ['r', 'g', 'b']):
  727. assert mpl.colors.same_color(text.get_color(), color)
  728. def test_legend_pathcollection_labelcolor_markeredgecolor():
  729. # test the labelcolor for labelcolor='markeredgecolor' on PathCollection
  730. fig, ax = plt.subplots()
  731. ax.scatter(np.arange(10), np.arange(10)*1, label='#1', edgecolor='r')
  732. ax.scatter(np.arange(10), np.arange(10)*2, label='#2', edgecolor='g')
  733. ax.scatter(np.arange(10), np.arange(10)*3, label='#3', edgecolor='b')
  734. leg = ax.legend(labelcolor='markeredgecolor')
  735. for text, color in zip(leg.get_texts(), ['r', 'g', 'b']):
  736. assert mpl.colors.same_color(text.get_color(), color)
  737. def test_legend_pathcollection_labelcolor_markeredgecolor_iterable():
  738. # test the labelcolor for labelcolor='markeredgecolor' on PathCollection
  739. # with iterable colors
  740. fig, ax = plt.subplots()
  741. colors = np.random.default_rng().choice(['r', 'g', 'b'], 10)
  742. ax.scatter(np.arange(10), np.arange(10)*1, label='#1', edgecolor=colors)
  743. leg = ax.legend(labelcolor='markeredgecolor')
  744. for text, color in zip(leg.get_texts(), ['k']):
  745. assert mpl.colors.same_color(text.get_color(), color)
  746. def test_legend_pathcollection_labelcolor_markeredgecolor_cmap():
  747. # test the labelcolor for labelcolor='markeredgecolor' on PathCollection
  748. # with a colormap
  749. fig, ax = plt.subplots()
  750. edgecolors = mpl.cm.viridis(np.random.rand(10))
  751. ax.scatter(
  752. np.arange(10),
  753. np.arange(10),
  754. label='#1',
  755. c=np.arange(10),
  756. edgecolor=edgecolors,
  757. cmap="Reds"
  758. )
  759. leg = ax.legend(labelcolor='markeredgecolor')
  760. for text, color in zip(leg.get_texts(), ['k']):
  761. assert mpl.colors.same_color(text.get_color(), color)
  762. def test_legend_labelcolor_markerfacecolor():
  763. # test the labelcolor for labelcolor='markerfacecolor'
  764. fig, ax = plt.subplots()
  765. ax.plot(np.arange(10), np.arange(10)*1, label='#1', markerfacecolor='r')
  766. ax.plot(np.arange(10), np.arange(10)*2, label='#2', markerfacecolor='g')
  767. ax.plot(np.arange(10), np.arange(10)*3, label='#3', markerfacecolor='b')
  768. leg = ax.legend(labelcolor='markerfacecolor')
  769. for text, color in zip(leg.get_texts(), ['r', 'g', 'b']):
  770. assert mpl.colors.same_color(text.get_color(), color)
  771. def test_legend_pathcollection_labelcolor_markerfacecolor():
  772. # test the labelcolor for labelcolor='markerfacecolor' on PathCollection
  773. fig, ax = plt.subplots()
  774. ax.scatter(np.arange(10), np.arange(10)*1, label='#1', facecolor='r')
  775. ax.scatter(np.arange(10), np.arange(10)*2, label='#2', facecolor='g')
  776. ax.scatter(np.arange(10), np.arange(10)*3, label='#3', facecolor='b')
  777. leg = ax.legend(labelcolor='markerfacecolor')
  778. for text, color in zip(leg.get_texts(), ['r', 'g', 'b']):
  779. assert mpl.colors.same_color(text.get_color(), color)
  780. def test_legend_pathcollection_labelcolor_markerfacecolor_iterable():
  781. # test the labelcolor for labelcolor='markerfacecolor' on PathCollection
  782. # with iterable colors
  783. fig, ax = plt.subplots()
  784. colors = np.random.default_rng().choice(['r', 'g', 'b'], 10)
  785. ax.scatter(np.arange(10), np.arange(10)*1, label='#1', facecolor=colors)
  786. leg = ax.legend(labelcolor='markerfacecolor')
  787. for text, color in zip(leg.get_texts(), ['k']):
  788. assert mpl.colors.same_color(text.get_color(), color)
  789. def test_legend_pathcollection_labelcolor_markfacecolor_cmap():
  790. # test the labelcolor for labelcolor='markerfacecolor' on PathCollection
  791. # with colormaps
  792. fig, ax = plt.subplots()
  793. facecolors = mpl.cm.viridis(np.random.rand(10))
  794. ax.scatter(
  795. np.arange(10),
  796. np.arange(10),
  797. label='#1',
  798. c=np.arange(10),
  799. facecolor=facecolors
  800. )
  801. leg = ax.legend(labelcolor='markerfacecolor')
  802. for text, color in zip(leg.get_texts(), ['k']):
  803. assert mpl.colors.same_color(text.get_color(), color)
  804. @pytest.mark.parametrize('color', ('red', 'none', (.5, .5, .5)))
  805. def test_legend_labelcolor_rcparam_single(color):
  806. # test the rcParams legend.labelcolor for a single color
  807. fig, ax = plt.subplots()
  808. ax.plot(np.arange(10), np.arange(10)*1, label='#1')
  809. ax.plot(np.arange(10), np.arange(10)*2, label='#2')
  810. ax.plot(np.arange(10), np.arange(10)*3, label='#3')
  811. mpl.rcParams['legend.labelcolor'] = color
  812. leg = ax.legend()
  813. for text in leg.get_texts():
  814. assert mpl.colors.same_color(text.get_color(), color)
  815. def test_legend_labelcolor_rcparam_linecolor():
  816. # test the rcParams legend.labelcolor for a linecolor
  817. fig, ax = plt.subplots()
  818. ax.plot(np.arange(10), np.arange(10)*1, label='#1', color='r')
  819. ax.plot(np.arange(10), np.arange(10)*2, label='#2', color='g')
  820. ax.plot(np.arange(10), np.arange(10)*3, label='#3', color='b')
  821. mpl.rcParams['legend.labelcolor'] = 'linecolor'
  822. leg = ax.legend()
  823. for text, color in zip(leg.get_texts(), ['r', 'g', 'b']):
  824. assert mpl.colors.same_color(text.get_color(), color)
  825. def test_legend_labelcolor_rcparam_markeredgecolor():
  826. # test the labelcolor for labelcolor='markeredgecolor'
  827. fig, ax = plt.subplots()
  828. ax.plot(np.arange(10), np.arange(10)*1, label='#1', markeredgecolor='r')
  829. ax.plot(np.arange(10), np.arange(10)*2, label='#2', markeredgecolor='g')
  830. ax.plot(np.arange(10), np.arange(10)*3, label='#3', markeredgecolor='b')
  831. mpl.rcParams['legend.labelcolor'] = 'markeredgecolor'
  832. leg = ax.legend()
  833. for text, color in zip(leg.get_texts(), ['r', 'g', 'b']):
  834. assert mpl.colors.same_color(text.get_color(), color)
  835. def test_legend_labelcolor_rcparam_markeredgecolor_short():
  836. # test the labelcolor for labelcolor='markeredgecolor'
  837. fig, ax = plt.subplots()
  838. ax.plot(np.arange(10), np.arange(10)*1, label='#1', markeredgecolor='r')
  839. ax.plot(np.arange(10), np.arange(10)*2, label='#2', markeredgecolor='g')
  840. ax.plot(np.arange(10), np.arange(10)*3, label='#3', markeredgecolor='b')
  841. mpl.rcParams['legend.labelcolor'] = 'mec'
  842. leg = ax.legend()
  843. for text, color in zip(leg.get_texts(), ['r', 'g', 'b']):
  844. assert mpl.colors.same_color(text.get_color(), color)
  845. def test_legend_labelcolor_rcparam_markerfacecolor():
  846. # test the labelcolor for labelcolor='markeredgecolor'
  847. fig, ax = plt.subplots()
  848. ax.plot(np.arange(10), np.arange(10)*1, label='#1', markerfacecolor='r')
  849. ax.plot(np.arange(10), np.arange(10)*2, label='#2', markerfacecolor='g')
  850. ax.plot(np.arange(10), np.arange(10)*3, label='#3', markerfacecolor='b')
  851. mpl.rcParams['legend.labelcolor'] = 'markerfacecolor'
  852. leg = ax.legend()
  853. for text, color in zip(leg.get_texts(), ['r', 'g', 'b']):
  854. assert mpl.colors.same_color(text.get_color(), color)
  855. def test_legend_labelcolor_rcparam_markerfacecolor_short():
  856. # test the labelcolor for labelcolor='markeredgecolor'
  857. fig, ax = plt.subplots()
  858. ax.plot(np.arange(10), np.arange(10)*1, label='#1', markerfacecolor='r')
  859. ax.plot(np.arange(10), np.arange(10)*2, label='#2', markerfacecolor='g')
  860. ax.plot(np.arange(10), np.arange(10)*3, label='#3', markerfacecolor='b')
  861. mpl.rcParams['legend.labelcolor'] = 'mfc'
  862. leg = ax.legend()
  863. for text, color in zip(leg.get_texts(), ['r', 'g', 'b']):
  864. assert mpl.colors.same_color(text.get_color(), color)
  865. def test_get_set_draggable():
  866. legend = plt.legend()
  867. assert not legend.get_draggable()
  868. legend.set_draggable(True)
  869. assert legend.get_draggable()
  870. legend.set_draggable(False)
  871. assert not legend.get_draggable()
  872. @pytest.mark.parametrize('draggable', (True, False))
  873. def test_legend_draggable(draggable):
  874. fig, ax = plt.subplots()
  875. ax.plot(range(10), label='shabnams')
  876. leg = ax.legend(draggable=draggable)
  877. assert leg.get_draggable() is draggable
  878. def test_alpha_handles():
  879. x, n, hh = plt.hist([1, 2, 3], alpha=0.25, label='data', color='red')
  880. legend = plt.legend()
  881. for lh in legend.legend_handles:
  882. lh.set_alpha(1.0)
  883. assert lh.get_facecolor()[:-1] == hh[1].get_facecolor()[:-1]
  884. assert lh.get_edgecolor()[:-1] == hh[1].get_edgecolor()[:-1]
  885. @needs_usetex
  886. def test_usetex_no_warn(caplog):
  887. mpl.rcParams['font.family'] = 'serif'
  888. mpl.rcParams['font.serif'] = 'Computer Modern'
  889. mpl.rcParams['text.usetex'] = True
  890. fig, ax = plt.subplots()
  891. ax.plot(0, 0, label='input')
  892. ax.legend(title="My legend")
  893. fig.canvas.draw()
  894. assert "Font family ['serif'] not found." not in caplog.text
  895. def test_warn_big_data_best_loc():
  896. fig, ax = plt.subplots()
  897. fig.canvas.draw() # So that we can call draw_artist later.
  898. for idx in range(1000):
  899. ax.plot(np.arange(5000), label=idx)
  900. with rc_context({'legend.loc': 'best'}):
  901. legend = ax.legend()
  902. with pytest.warns(UserWarning) as records:
  903. fig.draw_artist(legend) # Don't bother drawing the lines -- it's slow.
  904. # The _find_best_position method of Legend is called twice, duplicating
  905. # the warning message.
  906. assert len(records) == 2
  907. for record in records:
  908. assert str(record.message) == (
  909. 'Creating legend with loc="best" can be slow with large '
  910. 'amounts of data.')
  911. def test_no_warn_big_data_when_loc_specified():
  912. fig, ax = plt.subplots()
  913. fig.canvas.draw()
  914. for idx in range(1000):
  915. ax.plot(np.arange(5000), label=idx)
  916. legend = ax.legend('best')
  917. fig.draw_artist(legend) # Check that no warning is emitted.
  918. @pytest.mark.parametrize('label_array', [['low', 'high'],
  919. ('low', 'high'),
  920. np.array(['low', 'high'])])
  921. def test_plot_multiple_input_multiple_label(label_array):
  922. # test ax.plot() with multidimensional input
  923. # and multiple labels
  924. x = [1, 2, 3]
  925. y = [[1, 2],
  926. [2, 5],
  927. [4, 9]]
  928. fig, ax = plt.subplots()
  929. ax.plot(x, y, label=label_array)
  930. leg = ax.legend()
  931. legend_texts = [entry.get_text() for entry in leg.get_texts()]
  932. assert legend_texts == ['low', 'high']
  933. @pytest.mark.parametrize('label', ['one', 1, int])
  934. def test_plot_multiple_input_single_label(label):
  935. # test ax.plot() with multidimensional input
  936. # and single label
  937. x = [1, 2, 3]
  938. y = [[1, 2],
  939. [2, 5],
  940. [4, 9]]
  941. fig, ax = plt.subplots()
  942. ax.plot(x, y, label=label)
  943. leg = ax.legend()
  944. legend_texts = [entry.get_text() for entry in leg.get_texts()]
  945. assert legend_texts == [str(label)] * 2
  946. @pytest.mark.parametrize('label_array', [['low', 'high'],
  947. ('low', 'high'),
  948. np.array(['low', 'high'])])
  949. def test_plot_single_input_multiple_label(label_array):
  950. # test ax.plot() with 1D array like input
  951. # and iterable label
  952. x = [1, 2, 3]
  953. y = [2, 5, 6]
  954. fig, ax = plt.subplots()
  955. ax.plot(x, y, label=label_array)
  956. leg = ax.legend()
  957. assert len(leg.get_texts()) == 1
  958. assert leg.get_texts()[0].get_text() == str(label_array)
  959. def test_plot_multiple_label_incorrect_length_exception():
  960. # check that exception is raised if multiple labels
  961. # are given, but number of on labels != number of lines
  962. with pytest.raises(ValueError):
  963. x = [1, 2, 3]
  964. y = [[1, 2],
  965. [2, 5],
  966. [4, 9]]
  967. label = ['high', 'low', 'medium']
  968. fig, ax = plt.subplots()
  969. ax.plot(x, y, label=label)
  970. def test_legend_face_edgecolor():
  971. # Smoke test for PolyCollection legend handler with 'face' edgecolor.
  972. fig, ax = plt.subplots()
  973. ax.fill_between([0, 1, 2], [1, 2, 3], [2, 3, 4],
  974. facecolor='r', edgecolor='face', label='Fill')
  975. ax.legend()
  976. def test_legend_text_axes():
  977. fig, ax = plt.subplots()
  978. ax.plot([1, 2], [3, 4], label='line')
  979. leg = ax.legend()
  980. assert leg.axes is ax
  981. assert leg.get_texts()[0].axes is ax
  982. def test_handlerline2d():
  983. # Test marker consistency for monolithic Line2D legend handler (#11357).
  984. fig, ax = plt.subplots()
  985. ax.scatter([0, 1], [0, 1], marker="v")
  986. handles = [mlines.Line2D([0], [0], marker="v")]
  987. leg = ax.legend(handles, ["Aardvark"], numpoints=1)
  988. assert handles[0].get_marker() == leg.legend_handles[0].get_marker()
  989. def test_subfigure_legend():
  990. # Test that legend can be added to subfigure (#20723)
  991. subfig = plt.figure().subfigures()
  992. ax = subfig.subplots()
  993. ax.plot([0, 1], [0, 1], label="line")
  994. leg = subfig.legend()
  995. assert leg.figure is subfig
  996. def test_setting_alpha_keeps_polycollection_color():
  997. pc = plt.fill_between([0, 1], [2, 3], color='#123456', label='label')
  998. patch = plt.legend().get_patches()[0]
  999. patch.set_alpha(0.5)
  1000. assert patch.get_facecolor()[:3] == tuple(pc.get_facecolor()[0][:3])
  1001. assert patch.get_edgecolor()[:3] == tuple(pc.get_edgecolor()[0][:3])
  1002. def test_legend_markers_from_line2d():
  1003. # Test that markers can be copied for legend lines (#17960)
  1004. _markers = ['.', '*', 'v']
  1005. fig, ax = plt.subplots()
  1006. lines = [mlines.Line2D([0], [0], ls='None', marker=mark)
  1007. for mark in _markers]
  1008. labels = ["foo", "bar", "xyzzy"]
  1009. markers = [line.get_marker() for line in lines]
  1010. legend = ax.legend(lines, labels)
  1011. new_markers = [line.get_marker() for line in legend.get_lines()]
  1012. new_labels = [text.get_text() for text in legend.get_texts()]
  1013. assert markers == new_markers == _markers
  1014. assert labels == new_labels
  1015. @check_figures_equal()
  1016. def test_ncol_ncols(fig_test, fig_ref):
  1017. # Test that both ncol and ncols work
  1018. strings = ["a", "b", "c", "d", "e", "f"]
  1019. ncols = 3
  1020. fig_test.legend(strings, ncol=ncols)
  1021. fig_ref.legend(strings, ncols=ncols)
  1022. def test_loc_invalid_tuple_exception():
  1023. # check that exception is raised if the loc arg
  1024. # of legend is not a 2-tuple of numbers
  1025. fig, ax = plt.subplots()
  1026. with pytest.raises(ValueError, match=('loc must be string, coordinate '
  1027. 'tuple, or an integer 0-10, not \\(1.1,\\)')):
  1028. ax.legend(loc=(1.1, ))
  1029. with pytest.raises(ValueError, match=('loc must be string, coordinate '
  1030. 'tuple, or an integer 0-10, not \\(0.481, 0.4227, 0.4523\\)')):
  1031. ax.legend(loc=(0.481, 0.4227, 0.4523))
  1032. with pytest.raises(ValueError, match=('loc must be string, coordinate '
  1033. 'tuple, or an integer 0-10, not \\(0.481, \'go blue\'\\)')):
  1034. ax.legend(loc=(0.481, "go blue"))
  1035. def test_loc_valid_tuple():
  1036. fig, ax = plt.subplots()
  1037. ax.legend(loc=(0.481, 0.442))
  1038. ax.legend(loc=(1, 2))
  1039. def test_loc_valid_list():
  1040. fig, ax = plt.subplots()
  1041. ax.legend(loc=[0.481, 0.442])
  1042. ax.legend(loc=[1, 2])
  1043. def test_loc_invalid_list_exception():
  1044. fig, ax = plt.subplots()
  1045. with pytest.raises(ValueError, match=('loc must be string, coordinate '
  1046. 'tuple, or an integer 0-10, not \\[1.1, 2.2, 3.3\\]')):
  1047. ax.legend(loc=[1.1, 2.2, 3.3])
  1048. def test_loc_invalid_type():
  1049. fig, ax = plt.subplots()
  1050. with pytest.raises(ValueError, match=("loc must be string, coordinate "
  1051. "tuple, or an integer 0-10, not {'not': True}")):
  1052. ax.legend(loc={'not': True})
  1053. def test_loc_validation_numeric_value():
  1054. fig, ax = plt.subplots()
  1055. ax.legend(loc=0)
  1056. ax.legend(loc=1)
  1057. ax.legend(loc=5)
  1058. ax.legend(loc=10)
  1059. with pytest.raises(ValueError, match=('loc must be string, coordinate '
  1060. 'tuple, or an integer 0-10, not 11')):
  1061. ax.legend(loc=11)
  1062. with pytest.raises(ValueError, match=('loc must be string, coordinate '
  1063. 'tuple, or an integer 0-10, not -1')):
  1064. ax.legend(loc=-1)
  1065. def test_loc_validation_string_value():
  1066. fig, ax = plt.subplots()
  1067. ax.legend(loc='best')
  1068. ax.legend(loc='upper right')
  1069. ax.legend(loc='best')
  1070. ax.legend(loc='upper right')
  1071. ax.legend(loc='upper left')
  1072. ax.legend(loc='lower left')
  1073. ax.legend(loc='lower right')
  1074. ax.legend(loc='right')
  1075. ax.legend(loc='center left')
  1076. ax.legend(loc='center right')
  1077. ax.legend(loc='lower center')
  1078. ax.legend(loc='upper center')
  1079. with pytest.raises(ValueError, match="'wrong' is not a valid value for"):
  1080. ax.legend(loc='wrong')