test_legend.py 54 KB

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