test_text.py 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703
  1. from datetime import datetime
  2. import io
  3. import warnings
  4. import numpy as np
  5. from numpy.testing import assert_almost_equal
  6. import pytest
  7. import matplotlib as mpl
  8. from matplotlib.backend_bases import MouseEvent
  9. from matplotlib.font_manager import FontProperties
  10. import matplotlib.patches as mpatches
  11. import matplotlib.pyplot as plt
  12. from matplotlib.testing.decorators import check_figures_equal, image_comparison
  13. from matplotlib.text import Text
  14. needs_usetex = pytest.mark.skipif(
  15. not mpl.checkdep_usetex(True),
  16. reason="This test needs a TeX installation")
  17. @image_comparison(['font_styles'])
  18. def test_font_styles():
  19. def find_matplotlib_font(**kw):
  20. prop = FontProperties(**kw)
  21. path = findfont(prop, directory=mpl.get_data_path())
  22. return FontProperties(fname=path)
  23. from matplotlib.font_manager import FontProperties, findfont
  24. warnings.filterwarnings(
  25. 'ignore',
  26. r"findfont: Font family \[u?'Foo'\] not found. Falling back to .",
  27. UserWarning,
  28. module='matplotlib.font_manager')
  29. plt.figure()
  30. ax = plt.subplot(1, 1, 1)
  31. normalFont = find_matplotlib_font(
  32. family="sans-serif",
  33. style="normal",
  34. variant="normal",
  35. size=14)
  36. ax.annotate(
  37. "Normal Font",
  38. (0.1, 0.1),
  39. xycoords='axes fraction',
  40. fontproperties=normalFont)
  41. boldFont = find_matplotlib_font(
  42. family="Foo",
  43. style="normal",
  44. variant="normal",
  45. weight="bold",
  46. stretch=500,
  47. size=14)
  48. ax.annotate(
  49. "Bold Font",
  50. (0.1, 0.2),
  51. xycoords='axes fraction',
  52. fontproperties=boldFont)
  53. boldItemFont = find_matplotlib_font(
  54. family="sans serif",
  55. style="italic",
  56. variant="normal",
  57. weight=750,
  58. stretch=500,
  59. size=14)
  60. ax.annotate(
  61. "Bold Italic Font",
  62. (0.1, 0.3),
  63. xycoords='axes fraction',
  64. fontproperties=boldItemFont)
  65. lightFont = find_matplotlib_font(
  66. family="sans-serif",
  67. style="normal",
  68. variant="normal",
  69. weight=200,
  70. stretch=500,
  71. size=14)
  72. ax.annotate(
  73. "Light Font",
  74. (0.1, 0.4),
  75. xycoords='axes fraction',
  76. fontproperties=lightFont)
  77. condensedFont = find_matplotlib_font(
  78. family="sans-serif",
  79. style="normal",
  80. variant="normal",
  81. weight=500,
  82. stretch=100,
  83. size=14)
  84. ax.annotate(
  85. "Condensed Font",
  86. (0.1, 0.5),
  87. xycoords='axes fraction',
  88. fontproperties=condensedFont)
  89. ax.set_xticks([])
  90. ax.set_yticks([])
  91. @image_comparison(['multiline'])
  92. def test_multiline():
  93. plt.figure()
  94. ax = plt.subplot(1, 1, 1)
  95. ax.set_title("multiline\ntext alignment")
  96. plt.text(
  97. 0.2, 0.5, "TpTpTp\n$M$\nTpTpTp", size=20, ha="center", va="top")
  98. plt.text(
  99. 0.5, 0.5, "TpTpTp\n$M^{M^{M^{M}}}$\nTpTpTp", size=20,
  100. ha="center", va="top")
  101. plt.text(
  102. 0.8, 0.5, "TpTpTp\n$M_{q_{q_{q}}}$\nTpTpTp", size=20,
  103. ha="center", va="top")
  104. plt.xlim(0, 1)
  105. plt.ylim(0, 0.8)
  106. ax.set_xticks([])
  107. ax.set_yticks([])
  108. @image_comparison(['multiline2'], style='mpl20')
  109. def test_multiline2():
  110. # Remove this line when this test image is regenerated.
  111. plt.rcParams['text.kerning_factor'] = 6
  112. fig, ax = plt.subplots()
  113. ax.set_xlim([0, 1.4])
  114. ax.set_ylim([0, 2])
  115. ax.axhline(0.5, color='C2', linewidth=0.3)
  116. sts = ['Line', '2 Lineg\n 2 Lg', '$\\sum_i x $', 'hi $\\sum_i x $\ntest',
  117. 'test\n $\\sum_i x $', '$\\sum_i x $\n $\\sum_i x $']
  118. renderer = fig.canvas.get_renderer()
  119. def draw_box(ax, tt):
  120. r = mpatches.Rectangle((0, 0), 1, 1, clip_on=False,
  121. transform=ax.transAxes)
  122. r.set_bounds(
  123. tt.get_window_extent(renderer)
  124. .transformed(ax.transAxes.inverted())
  125. .bounds)
  126. ax.add_patch(r)
  127. horal = 'left'
  128. for nn, st in enumerate(sts):
  129. tt = ax.text(0.2 * nn + 0.1, 0.5, st, horizontalalignment=horal,
  130. verticalalignment='bottom')
  131. draw_box(ax, tt)
  132. ax.text(1.2, 0.5, 'Bottom align', color='C2')
  133. ax.axhline(1.3, color='C2', linewidth=0.3)
  134. for nn, st in enumerate(sts):
  135. tt = ax.text(0.2 * nn + 0.1, 1.3, st, horizontalalignment=horal,
  136. verticalalignment='top')
  137. draw_box(ax, tt)
  138. ax.text(1.2, 1.3, 'Top align', color='C2')
  139. ax.axhline(1.8, color='C2', linewidth=0.3)
  140. for nn, st in enumerate(sts):
  141. tt = ax.text(0.2 * nn + 0.1, 1.8, st, horizontalalignment=horal,
  142. verticalalignment='baseline')
  143. draw_box(ax, tt)
  144. ax.text(1.2, 1.8, 'Baseline align', color='C2')
  145. ax.axhline(0.1, color='C2', linewidth=0.3)
  146. for nn, st in enumerate(sts):
  147. tt = ax.text(0.2 * nn + 0.1, 0.1, st, horizontalalignment=horal,
  148. verticalalignment='bottom', rotation=20)
  149. draw_box(ax, tt)
  150. ax.text(1.2, 0.1, 'Bot align, rot20', color='C2')
  151. @image_comparison(['antialiased.png'])
  152. def test_antialiasing():
  153. mpl.rcParams['text.antialiased'] = True
  154. fig = plt.figure(figsize=(5.25, 0.75))
  155. fig.text(0.5, 0.75, "antialiased", horizontalalignment='center',
  156. verticalalignment='center')
  157. fig.text(0.5, 0.25, r"$\sqrt{x}$", horizontalalignment='center',
  158. verticalalignment='center')
  159. # NOTE: We don't need to restore the rcParams here, because the
  160. # test cleanup will do it for us. In fact, if we do it here, it
  161. # will turn antialiasing back off before the images are actually
  162. # rendered.
  163. def test_afm_kerning():
  164. fn = mpl.font_manager.findfont("Helvetica", fontext="afm")
  165. with open(fn, 'rb') as fh:
  166. afm = mpl.afm.AFM(fh)
  167. assert afm.string_width_height('VAVAVAVAVAVA') == (7174.0, 718)
  168. @image_comparison(['text_contains.png'])
  169. def test_contains():
  170. fig = plt.figure()
  171. ax = plt.axes()
  172. mevent = MouseEvent('button_press_event', fig.canvas, 0.5, 0.5, 1, None)
  173. xs = np.linspace(0.25, 0.75, 30)
  174. ys = np.linspace(0.25, 0.75, 30)
  175. xs, ys = np.meshgrid(xs, ys)
  176. txt = plt.text(
  177. 0.5, 0.4, 'hello world', ha='center', fontsize=30, rotation=30)
  178. # uncomment to draw the text's bounding box
  179. # txt.set_bbox(dict(edgecolor='black', facecolor='none'))
  180. # draw the text. This is important, as the contains method can only work
  181. # when a renderer exists.
  182. fig.canvas.draw()
  183. for x, y in zip(xs.flat, ys.flat):
  184. mevent.x, mevent.y = plt.gca().transAxes.transform([x, y])
  185. contains, _ = txt.contains(mevent)
  186. color = 'yellow' if contains else 'red'
  187. # capture the viewLim, plot a point, and reset the viewLim
  188. vl = ax.viewLim.frozen()
  189. ax.plot(x, y, 'o', color=color)
  190. ax.viewLim.set(vl)
  191. def test_annotation_contains():
  192. # Check that Annotation.contains looks at the bboxes of the text and the
  193. # arrow separately, not at the joint bbox.
  194. fig, ax = plt.subplots()
  195. ann = ax.annotate(
  196. "hello", xy=(.4, .4), xytext=(.6, .6), arrowprops={"arrowstyle": "->"})
  197. fig.canvas.draw() # Needed for the same reason as in test_contains.
  198. event = MouseEvent(
  199. "button_press_event", fig.canvas, *ax.transData.transform((.5, .6)))
  200. assert ann.contains(event) == (False, {})
  201. @image_comparison(['titles'])
  202. def test_titles():
  203. # left and right side titles
  204. plt.figure()
  205. ax = plt.subplot(1, 1, 1)
  206. ax.set_title("left title", loc="left")
  207. ax.set_title("right title", loc="right")
  208. ax.set_xticks([])
  209. ax.set_yticks([])
  210. @image_comparison(['text_alignment'], style='mpl20')
  211. def test_alignment():
  212. plt.figure()
  213. ax = plt.subplot(1, 1, 1)
  214. x = 0.1
  215. for rotation in (0, 30):
  216. for alignment in ('top', 'bottom', 'baseline', 'center'):
  217. ax.text(
  218. x, 0.5, alignment + " Tj", va=alignment, rotation=rotation,
  219. bbox=dict(boxstyle='round', facecolor='wheat', alpha=0.5))
  220. ax.text(
  221. x, 1.0, r'$\sum_{i=0}^{j}$', va=alignment, rotation=rotation)
  222. x += 0.1
  223. ax.plot([0, 1], [0.5, 0.5])
  224. ax.plot([0, 1], [1.0, 1.0])
  225. ax.set_xlim([0, 1])
  226. ax.set_ylim([0, 1.5])
  227. ax.set_xticks([])
  228. ax.set_yticks([])
  229. @image_comparison(['axes_titles.png'])
  230. def test_axes_titles():
  231. # Related to issue #3327
  232. plt.figure()
  233. ax = plt.subplot(1, 1, 1)
  234. ax.set_title('center', loc='center', fontsize=20, fontweight=700)
  235. ax.set_title('left', loc='left', fontsize=12, fontweight=400)
  236. ax.set_title('right', loc='right', fontsize=12, fontweight=400)
  237. def test_set_position():
  238. fig, ax = plt.subplots()
  239. # test set_position
  240. ann = ax.annotate(
  241. 'test', (0, 0), xytext=(0, 0), textcoords='figure pixels')
  242. fig.canvas.draw()
  243. init_pos = ann.get_window_extent(fig.canvas.renderer)
  244. shift_val = 15
  245. ann.set_position((shift_val, shift_val))
  246. fig.canvas.draw()
  247. post_pos = ann.get_window_extent(fig.canvas.renderer)
  248. for a, b in zip(init_pos.min, post_pos.min):
  249. assert a + shift_val == b
  250. # test xyann
  251. ann = ax.annotate(
  252. 'test', (0, 0), xytext=(0, 0), textcoords='figure pixels')
  253. fig.canvas.draw()
  254. init_pos = ann.get_window_extent(fig.canvas.renderer)
  255. shift_val = 15
  256. ann.xyann = (shift_val, shift_val)
  257. fig.canvas.draw()
  258. post_pos = ann.get_window_extent(fig.canvas.renderer)
  259. for a, b in zip(init_pos.min, post_pos.min):
  260. assert a + shift_val == b
  261. @pytest.mark.parametrize('text', ['', 'O'], ids=['empty', 'non-empty'])
  262. def test_non_default_dpi(text):
  263. fig, ax = plt.subplots()
  264. t1 = ax.text(0.5, 0.5, text, ha='left', va='bottom')
  265. fig.canvas.draw()
  266. dpi = fig.dpi
  267. bbox1 = t1.get_window_extent()
  268. bbox2 = t1.get_window_extent(dpi=dpi * 10)
  269. np.testing.assert_allclose(bbox2.get_points(), bbox1.get_points() * 10,
  270. rtol=5e-2)
  271. # Text.get_window_extent should not permanently change dpi.
  272. assert fig.dpi == dpi
  273. def test_get_rotation_string():
  274. assert mpl.text.get_rotation('horizontal') == 0.
  275. assert mpl.text.get_rotation('vertical') == 90.
  276. assert mpl.text.get_rotation('15.') == 15.
  277. def test_get_rotation_float():
  278. for i in [15., 16.70, 77.4]:
  279. assert mpl.text.get_rotation(i) == i
  280. def test_get_rotation_int():
  281. for i in [67, 16, 41]:
  282. assert mpl.text.get_rotation(i) == float(i)
  283. def test_get_rotation_raises():
  284. with pytest.raises(ValueError):
  285. mpl.text.get_rotation('hozirontal')
  286. def test_get_rotation_none():
  287. assert mpl.text.get_rotation(None) == 0.0
  288. def test_get_rotation_mod360():
  289. for i, j in zip([360., 377., 720+177.2], [0., 17., 177.2]):
  290. assert_almost_equal(mpl.text.get_rotation(i), j)
  291. @pytest.mark.parametrize("ha", ["center", "right", "left"])
  292. @pytest.mark.parametrize("va", ["center", "top", "bottom",
  293. "baseline", "center_baseline"])
  294. def test_null_rotation_with_rotation_mode(ha, va):
  295. fig, ax = plt.subplots()
  296. kw = dict(rotation=0, va=va, ha=ha)
  297. t0 = ax.text(.5, .5, 'test', rotation_mode='anchor', **kw)
  298. t1 = ax.text(.5, .5, 'test', rotation_mode='default', **kw)
  299. fig.canvas.draw()
  300. assert_almost_equal(t0.get_window_extent(fig.canvas.renderer).get_points(),
  301. t1.get_window_extent(fig.canvas.renderer).get_points())
  302. @image_comparison(['text_bboxclip'])
  303. def test_bbox_clipping():
  304. plt.text(0.9, 0.2, 'Is bbox clipped?', backgroundcolor='r', clip_on=True)
  305. t = plt.text(0.9, 0.5, 'Is fancy bbox clipped?', clip_on=True)
  306. t.set_bbox({"boxstyle": "round, pad=0.1"})
  307. @image_comparison(['annotation_negative_ax_coords.png'])
  308. def test_annotation_negative_ax_coords():
  309. fig, ax = plt.subplots()
  310. ax.annotate('+ pts',
  311. xytext=[30, 20], textcoords='axes points',
  312. xy=[30, 20], xycoords='axes points', fontsize=32)
  313. ax.annotate('- pts',
  314. xytext=[30, -20], textcoords='axes points',
  315. xy=[30, -20], xycoords='axes points', fontsize=32,
  316. va='top')
  317. ax.annotate('+ frac',
  318. xytext=[0.75, 0.05], textcoords='axes fraction',
  319. xy=[0.75, 0.05], xycoords='axes fraction', fontsize=32)
  320. ax.annotate('- frac',
  321. xytext=[0.75, -0.05], textcoords='axes fraction',
  322. xy=[0.75, -0.05], xycoords='axes fraction', fontsize=32,
  323. va='top')
  324. ax.annotate('+ pixels',
  325. xytext=[160, 25], textcoords='axes pixels',
  326. xy=[160, 25], xycoords='axes pixels', fontsize=32)
  327. ax.annotate('- pixels',
  328. xytext=[160, -25], textcoords='axes pixels',
  329. xy=[160, -25], xycoords='axes pixels', fontsize=32,
  330. va='top')
  331. @image_comparison(['annotation_negative_fig_coords.png'])
  332. def test_annotation_negative_fig_coords():
  333. fig, ax = plt.subplots()
  334. ax.annotate('+ pts',
  335. xytext=[10, 120], textcoords='figure points',
  336. xy=[10, 120], xycoords='figure points', fontsize=32)
  337. ax.annotate('- pts',
  338. xytext=[-10, 180], textcoords='figure points',
  339. xy=[-10, 180], xycoords='figure points', fontsize=32,
  340. va='top')
  341. ax.annotate('+ frac',
  342. xytext=[0.05, 0.55], textcoords='figure fraction',
  343. xy=[0.05, 0.55], xycoords='figure fraction', fontsize=32)
  344. ax.annotate('- frac',
  345. xytext=[-0.05, 0.5], textcoords='figure fraction',
  346. xy=[-0.05, 0.5], xycoords='figure fraction', fontsize=32,
  347. va='top')
  348. ax.annotate('+ pixels',
  349. xytext=[50, 50], textcoords='figure pixels',
  350. xy=[50, 50], xycoords='figure pixels', fontsize=32)
  351. ax.annotate('- pixels',
  352. xytext=[-50, 100], textcoords='figure pixels',
  353. xy=[-50, 100], xycoords='figure pixels', fontsize=32,
  354. va='top')
  355. def test_text_stale():
  356. fig, (ax1, ax2) = plt.subplots(1, 2)
  357. plt.draw_all()
  358. assert not ax1.stale
  359. assert not ax2.stale
  360. assert not fig.stale
  361. txt1 = ax1.text(.5, .5, 'aardvark')
  362. assert ax1.stale
  363. assert txt1.stale
  364. assert fig.stale
  365. ann1 = ax2.annotate('aardvark', xy=[.5, .5])
  366. assert ax2.stale
  367. assert ann1.stale
  368. assert fig.stale
  369. plt.draw_all()
  370. assert not ax1.stale
  371. assert not ax2.stale
  372. assert not fig.stale
  373. @image_comparison(['agg_text_clip.png'])
  374. def test_agg_text_clip():
  375. np.random.seed(1)
  376. fig, (ax1, ax2) = plt.subplots(2)
  377. for x, y in np.random.rand(10, 2):
  378. ax1.text(x, y, "foo", clip_on=True)
  379. ax2.text(x, y, "foo")
  380. def test_text_size_binding():
  381. mpl.rcParams['font.size'] = 10
  382. fp = mpl.font_manager.FontProperties(size='large')
  383. sz1 = fp.get_size_in_points()
  384. mpl.rcParams['font.size'] = 100
  385. assert sz1 == fp.get_size_in_points()
  386. @image_comparison(['font_scaling.pdf'])
  387. def test_font_scaling():
  388. mpl.rcParams['pdf.fonttype'] = 42
  389. fig, ax = plt.subplots(figsize=(6.4, 12.4))
  390. ax.xaxis.set_major_locator(plt.NullLocator())
  391. ax.yaxis.set_major_locator(plt.NullLocator())
  392. ax.set_ylim(-10, 600)
  393. for i, fs in enumerate(range(4, 43, 2)):
  394. ax.text(0.1, i*30, "{fs} pt font size".format(fs=fs), fontsize=fs)
  395. @pytest.mark.parametrize('spacing1, spacing2', [(0.4, 2), (2, 0.4), (2, 2)])
  396. def test_two_2line_texts(spacing1, spacing2):
  397. text_string = 'line1\nline2'
  398. fig = plt.figure()
  399. renderer = fig.canvas.get_renderer()
  400. text1 = plt.text(0.25, 0.5, text_string, linespacing=spacing1)
  401. text2 = plt.text(0.25, 0.5, text_string, linespacing=spacing2)
  402. fig.canvas.draw()
  403. box1 = text1.get_window_extent(renderer=renderer)
  404. box2 = text2.get_window_extent(renderer=renderer)
  405. # line spacing only affects height
  406. assert box1.width == box2.width
  407. if spacing1 == spacing2:
  408. assert box1.height == box2.height
  409. else:
  410. assert box1.height != box2.height
  411. def test_nonfinite_pos():
  412. fig, ax = plt.subplots()
  413. ax.text(0, np.nan, 'nan')
  414. ax.text(np.inf, 0, 'inf')
  415. fig.canvas.draw()
  416. def test_hinting_factor_backends():
  417. plt.rcParams['text.hinting_factor'] = 1
  418. fig = plt.figure()
  419. t = fig.text(0.5, 0.5, 'some text')
  420. fig.savefig(io.BytesIO(), format='svg')
  421. expected = t.get_window_extent().intervalx
  422. fig.savefig(io.BytesIO(), format='png')
  423. # Backends should apply hinting_factor consistently (within 10%).
  424. np.testing.assert_allclose(t.get_window_extent().intervalx, expected,
  425. rtol=0.1)
  426. @needs_usetex
  427. def test_usetex_is_copied():
  428. # Indirectly tests that update_from (which is used to copy tick label
  429. # properties) copies usetex state.
  430. fig = plt.figure()
  431. plt.rcParams["text.usetex"] = False
  432. ax1 = fig.add_subplot(121)
  433. plt.rcParams["text.usetex"] = True
  434. ax2 = fig.add_subplot(122)
  435. fig.canvas.draw()
  436. for ax, usetex in [(ax1, False), (ax2, True)]:
  437. for t in ax.xaxis.majorTicks:
  438. assert t.label1.get_usetex() == usetex
  439. @needs_usetex
  440. def test_single_artist_usetex():
  441. # Check that a single artist marked with usetex does not get passed through
  442. # the mathtext parser at all (for the Agg backend) (the mathtext parser
  443. # currently fails to parse \frac12, requiring \frac{1}{2} instead).
  444. fig = plt.figure()
  445. fig.text(.5, .5, r"$\frac12$", usetex=True)
  446. fig.canvas.draw()
  447. @pytest.mark.parametrize("fmt", ["png", "pdf", "svg"])
  448. def test_single_artist_usenotex(fmt):
  449. # Check that a single artist can be marked as not-usetex even though the
  450. # rcParam is on ("2_2_2" fails if passed to TeX). This currently skips
  451. # postscript output as the ps renderer doesn't support mixing usetex and
  452. # non-usetex.
  453. plt.rcParams["text.usetex"] = True
  454. fig = plt.figure()
  455. fig.text(.5, .5, "2_2_2", usetex=False)
  456. fig.savefig(io.BytesIO(), format=fmt)
  457. @image_comparison(['text_as_path_opacity.svg'])
  458. def test_text_as_path_opacity():
  459. plt.figure()
  460. plt.gca().set_axis_off()
  461. plt.text(0.25, 0.25, 'c', color=(0, 0, 0, 0.5))
  462. plt.text(0.25, 0.5, 'a', alpha=0.5)
  463. plt.text(0.25, 0.75, 'x', alpha=0.5, color=(0, 0, 0, 1))
  464. @image_comparison(['text_as_text_opacity.svg'])
  465. def test_text_as_text_opacity():
  466. mpl.rcParams['svg.fonttype'] = 'none'
  467. plt.figure()
  468. plt.gca().set_axis_off()
  469. plt.text(0.25, 0.25, '50% using `color`', color=(0, 0, 0, 0.5))
  470. plt.text(0.25, 0.5, '50% using `alpha`', alpha=0.5)
  471. plt.text(0.25, 0.75, '50% using `alpha` and 100% `color`', alpha=0.5,
  472. color=(0, 0, 0, 1))
  473. def test_text_repr():
  474. # smoketest to make sure text repr doesn't error for category
  475. plt.plot(['A', 'B'], [1, 2])
  476. txt = plt.text(['A'], 0.5, 'Boo')
  477. print(txt)
  478. def test_annotation_update():
  479. fig, ax = plt.subplots(1, 1)
  480. an = ax.annotate('annotation', xy=(0.5, 0.5))
  481. extent1 = an.get_window_extent(fig.canvas.get_renderer())
  482. fig.tight_layout()
  483. extent2 = an.get_window_extent(fig.canvas.get_renderer())
  484. assert not np.allclose(extent1.get_points(), extent2.get_points(),
  485. rtol=1e-6)
  486. @check_figures_equal(extensions=["png"])
  487. def test_annotation_units(fig_test, fig_ref):
  488. ax = fig_test.add_subplot()
  489. ax.plot(datetime.now(), 1, "o") # Implicitly set axes extents.
  490. ax.annotate("x", (datetime.now(), 0.5), xycoords=("data", "axes fraction"),
  491. # This used to crash before.
  492. xytext=(0, 0), textcoords="offset points")
  493. ax = fig_ref.add_subplot()
  494. ax.plot(datetime.now(), 1, "o")
  495. ax.annotate("x", (datetime.now(), 0.5), xycoords=("data", "axes fraction"))
  496. @image_comparison(['large_subscript_title.png'], style='mpl20')
  497. def test_large_subscript_title():
  498. # Remove this line when this test image is regenerated.
  499. plt.rcParams['text.kerning_factor'] = 6
  500. plt.rcParams['axes.titley'] = None
  501. fig, axs = plt.subplots(1, 2, figsize=(9, 2.5), constrained_layout=True)
  502. ax = axs[0]
  503. ax.set_title(r'$\sum_{i} x_i$')
  504. ax.set_title('New way', loc='left')
  505. ax.set_xticklabels('')
  506. ax = axs[1]
  507. tt = ax.set_title(r'$\sum_{i} x_i$', y=1.01)
  508. ax.set_title('Old Way', loc='left')
  509. ax.set_xticklabels('')
  510. def test_wrap():
  511. fig = plt.figure(figsize=(6, 4))
  512. s = 'This is a very long text that should be wrapped multiple times.'
  513. text = fig.text(0.7, 0.5, s, wrap=True)
  514. fig.canvas.draw()
  515. assert text._get_wrapped_text() == ('This is a very long\n'
  516. 'text that should be\n'
  517. 'wrapped multiple\n'
  518. 'times.')
  519. def test_long_word_wrap():
  520. fig = plt.figure(figsize=(6, 4))
  521. text = fig.text(9.5, 8, 'Alonglineoftexttowrap', wrap=True)
  522. fig.canvas.draw()
  523. assert text._get_wrapped_text() == 'Alonglineoftexttowrap'
  524. def test_wrap_no_wrap():
  525. fig = plt.figure(figsize=(6, 4))
  526. text = fig.text(0, 0, 'non wrapped text', wrap=True)
  527. fig.canvas.draw()
  528. assert text._get_wrapped_text() == 'non wrapped text'
  529. @check_figures_equal(extensions=["png"])
  530. def test_buffer_size(fig_test, fig_ref):
  531. # On old versions of the Agg renderer, large non-ascii single-character
  532. # strings (here, "€") would be rendered clipped because the rendering
  533. # buffer would be set by the physical size of the smaller "a" character.
  534. ax = fig_test.add_subplot()
  535. ax.set_yticks([0, 1])
  536. ax.set_yticklabels(["€", "a"])
  537. ax.yaxis.majorTicks[1].label1.set_color("w")
  538. ax = fig_ref.add_subplot()
  539. ax.set_yticks([0, 1])
  540. ax.set_yticklabels(["€", ""])
  541. def test_fontproperties_kwarg_precedence():
  542. """Test that kwargs take precedence over fontproperties defaults."""
  543. plt.figure()
  544. text1 = plt.xlabel("value", fontproperties='Times New Roman', size=40.0)
  545. text2 = plt.ylabel("counts", size=40.0, fontproperties='Times New Roman')
  546. assert text1.get_size() == 40.0
  547. assert text2.get_size() == 40.0
  548. def test_update_mutate_input():
  549. inp = dict(fontproperties=FontProperties(weight="bold"),
  550. bbox=None)
  551. cache = dict(inp)
  552. t = Text()
  553. t.update(inp)
  554. assert inp['fontproperties'] == cache['fontproperties']
  555. assert inp['bbox'] == cache['bbox']