test_text.py 38 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137
  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. from packaging.version import parse as parse_version
  7. import pyparsing
  8. import pytest
  9. import matplotlib as mpl
  10. from matplotlib.backend_bases import MouseEvent
  11. from matplotlib.backends.backend_agg import RendererAgg
  12. from matplotlib.figure import Figure
  13. from matplotlib.font_manager import FontProperties
  14. import matplotlib.patches as mpatches
  15. import matplotlib.pyplot as plt
  16. from matplotlib.gridspec import GridSpec
  17. import matplotlib.transforms as mtransforms
  18. from matplotlib.testing.decorators import check_figures_equal, image_comparison
  19. from matplotlib.testing._markers import needs_usetex
  20. from matplotlib.text import Text, Annotation, OffsetFrom
  21. pyparsing_version = parse_version(pyparsing.__version__)
  22. @image_comparison(['font_styles'])
  23. def test_font_styles():
  24. def find_matplotlib_font(**kw):
  25. prop = FontProperties(**kw)
  26. path = findfont(prop, directory=mpl.get_data_path())
  27. return FontProperties(fname=path)
  28. from matplotlib.font_manager import FontProperties, findfont
  29. warnings.filterwarnings(
  30. 'ignore',
  31. r"findfont: Font family \[u?'Foo'\] not found. Falling back to .",
  32. UserWarning,
  33. module='matplotlib.font_manager')
  34. fig, ax = plt.subplots()
  35. normal_font = find_matplotlib_font(
  36. family="sans-serif",
  37. style="normal",
  38. variant="normal",
  39. size=14)
  40. a = ax.annotate(
  41. "Normal Font",
  42. (0.1, 0.1),
  43. xycoords='axes fraction',
  44. fontproperties=normal_font)
  45. assert a.get_fontname() == 'DejaVu Sans'
  46. assert a.get_fontstyle() == 'normal'
  47. assert a.get_fontvariant() == 'normal'
  48. assert a.get_weight() == 'normal'
  49. assert a.get_stretch() == 'normal'
  50. bold_font = find_matplotlib_font(
  51. family="Foo",
  52. style="normal",
  53. variant="normal",
  54. weight="bold",
  55. stretch=500,
  56. size=14)
  57. ax.annotate(
  58. "Bold Font",
  59. (0.1, 0.2),
  60. xycoords='axes fraction',
  61. fontproperties=bold_font)
  62. bold_italic_font = find_matplotlib_font(
  63. family="sans serif",
  64. style="italic",
  65. variant="normal",
  66. weight=750,
  67. stretch=500,
  68. size=14)
  69. ax.annotate(
  70. "Bold Italic Font",
  71. (0.1, 0.3),
  72. xycoords='axes fraction',
  73. fontproperties=bold_italic_font)
  74. light_font = find_matplotlib_font(
  75. family="sans-serif",
  76. style="normal",
  77. variant="normal",
  78. weight=200,
  79. stretch=500,
  80. size=14)
  81. ax.annotate(
  82. "Light Font",
  83. (0.1, 0.4),
  84. xycoords='axes fraction',
  85. fontproperties=light_font)
  86. condensed_font = find_matplotlib_font(
  87. family="sans-serif",
  88. style="normal",
  89. variant="normal",
  90. weight=500,
  91. stretch=100,
  92. size=14)
  93. ax.annotate(
  94. "Condensed Font",
  95. (0.1, 0.5),
  96. xycoords='axes fraction',
  97. fontproperties=condensed_font)
  98. ax.set_xticks([])
  99. ax.set_yticks([])
  100. @image_comparison(['multiline'])
  101. def test_multiline():
  102. plt.figure()
  103. ax = plt.subplot(1, 1, 1)
  104. ax.set_title("multiline\ntext alignment")
  105. plt.text(
  106. 0.2, 0.5, "TpTpTp\n$M$\nTpTpTp", size=20, ha="center", va="top")
  107. plt.text(
  108. 0.5, 0.5, "TpTpTp\n$M^{M^{M^{M}}}$\nTpTpTp", size=20,
  109. ha="center", va="top")
  110. plt.text(
  111. 0.8, 0.5, "TpTpTp\n$M_{q_{q_{q}}}$\nTpTpTp", size=20,
  112. ha="center", va="top")
  113. plt.xlim(0, 1)
  114. plt.ylim(0, 0.8)
  115. ax.set_xticks([])
  116. ax.set_yticks([])
  117. @image_comparison(['multiline2'], style='mpl20')
  118. def test_multiline2():
  119. # Remove this line when this test image is regenerated.
  120. plt.rcParams['text.kerning_factor'] = 6
  121. fig, ax = plt.subplots()
  122. ax.set_xlim([0, 1.4])
  123. ax.set_ylim([0, 2])
  124. ax.axhline(0.5, color='C2', linewidth=0.3)
  125. sts = ['Line', '2 Lineg\n 2 Lg', '$\\sum_i x $', 'hi $\\sum_i x $\ntest',
  126. 'test\n $\\sum_i x $', '$\\sum_i x $\n $\\sum_i x $']
  127. renderer = fig.canvas.get_renderer()
  128. def draw_box(ax, tt):
  129. r = mpatches.Rectangle((0, 0), 1, 1, clip_on=False,
  130. transform=ax.transAxes)
  131. r.set_bounds(
  132. tt.get_window_extent(renderer)
  133. .transformed(ax.transAxes.inverted())
  134. .bounds)
  135. ax.add_patch(r)
  136. horal = 'left'
  137. for nn, st in enumerate(sts):
  138. tt = ax.text(0.2 * nn + 0.1, 0.5, st, horizontalalignment=horal,
  139. verticalalignment='bottom')
  140. draw_box(ax, tt)
  141. ax.text(1.2, 0.5, 'Bottom align', color='C2')
  142. ax.axhline(1.3, color='C2', linewidth=0.3)
  143. for nn, st in enumerate(sts):
  144. tt = ax.text(0.2 * nn + 0.1, 1.3, st, horizontalalignment=horal,
  145. verticalalignment='top')
  146. draw_box(ax, tt)
  147. ax.text(1.2, 1.3, 'Top align', color='C2')
  148. ax.axhline(1.8, color='C2', linewidth=0.3)
  149. for nn, st in enumerate(sts):
  150. tt = ax.text(0.2 * nn + 0.1, 1.8, st, horizontalalignment=horal,
  151. verticalalignment='baseline')
  152. draw_box(ax, tt)
  153. ax.text(1.2, 1.8, 'Baseline align', color='C2')
  154. ax.axhline(0.1, color='C2', linewidth=0.3)
  155. for nn, st in enumerate(sts):
  156. tt = ax.text(0.2 * nn + 0.1, 0.1, st, horizontalalignment=horal,
  157. verticalalignment='bottom', rotation=20)
  158. draw_box(ax, tt)
  159. ax.text(1.2, 0.1, 'Bot align, rot20', color='C2')
  160. @image_comparison(['antialiased.png'], style='mpl20')
  161. def test_antialiasing():
  162. mpl.rcParams['text.antialiased'] = False # Passed arguments should override.
  163. fig = plt.figure(figsize=(5.25, 0.75))
  164. fig.text(0.3, 0.75, "antialiased", horizontalalignment='center',
  165. verticalalignment='center', antialiased=True)
  166. fig.text(0.3, 0.25, r"$\sqrt{x}$", horizontalalignment='center',
  167. verticalalignment='center', antialiased=True)
  168. mpl.rcParams['text.antialiased'] = True # Passed arguments should override.
  169. fig.text(0.7, 0.75, "not antialiased", horizontalalignment='center',
  170. verticalalignment='center', antialiased=False)
  171. fig.text(0.7, 0.25, r"$\sqrt{x}$", horizontalalignment='center',
  172. verticalalignment='center', antialiased=False)
  173. mpl.rcParams['text.antialiased'] = False # Should not affect existing text.
  174. def test_afm_kerning():
  175. fn = mpl.font_manager.findfont("Helvetica", fontext="afm")
  176. with open(fn, 'rb') as fh:
  177. afm = mpl._afm.AFM(fh)
  178. assert afm.string_width_height('VAVAVAVAVAVA') == (7174.0, 718)
  179. @image_comparison(['text_contains.png'])
  180. def test_contains():
  181. fig = plt.figure()
  182. ax = plt.axes()
  183. mevent = MouseEvent('button_press_event', fig.canvas, 0.5, 0.5, 1, None)
  184. xs = np.linspace(0.25, 0.75, 30)
  185. ys = np.linspace(0.25, 0.75, 30)
  186. xs, ys = np.meshgrid(xs, ys)
  187. txt = plt.text(
  188. 0.5, 0.4, 'hello world', ha='center', fontsize=30, rotation=30)
  189. # uncomment to draw the text's bounding box
  190. # txt.set_bbox(dict(edgecolor='black', facecolor='none'))
  191. # draw the text. This is important, as the contains method can only work
  192. # when a renderer exists.
  193. fig.canvas.draw()
  194. for x, y in zip(xs.flat, ys.flat):
  195. mevent.x, mevent.y = plt.gca().transAxes.transform([x, y])
  196. contains, _ = txt.contains(mevent)
  197. color = 'yellow' if contains else 'red'
  198. # capture the viewLim, plot a point, and reset the viewLim
  199. vl = ax.viewLim.frozen()
  200. ax.plot(x, y, 'o', color=color)
  201. ax.viewLim.set(vl)
  202. def test_annotation_contains():
  203. # Check that Annotation.contains looks at the bboxes of the text and the
  204. # arrow separately, not at the joint bbox.
  205. fig, ax = plt.subplots()
  206. ann = ax.annotate(
  207. "hello", xy=(.4, .4), xytext=(.6, .6), arrowprops={"arrowstyle": "->"})
  208. fig.canvas.draw() # Needed for the same reason as in test_contains.
  209. event = MouseEvent(
  210. "button_press_event", fig.canvas, *ax.transData.transform((.5, .6)))
  211. assert ann.contains(event) == (False, {})
  212. @pytest.mark.parametrize('err, xycoords, match', (
  213. (TypeError, print, "xycoords callable must return a BboxBase or Transform, not a"),
  214. (TypeError, [0, 0], r"'xycoords' must be an instance of str, tuple"),
  215. (ValueError, "foo", "'foo' is not a valid coordinate"),
  216. (ValueError, "foo bar", "'foo bar' is not a valid coordinate"),
  217. (ValueError, "offset foo", "xycoords cannot be an offset coordinate"),
  218. (ValueError, "axes foo", "'foo' is not a recognized unit"),
  219. ))
  220. def test_annotate_errors(err, xycoords, match):
  221. fig, ax = plt.subplots()
  222. with pytest.raises(err, match=match):
  223. ax.annotate('xy', (0, 0), xytext=(0.5, 0.5), xycoords=xycoords)
  224. fig.canvas.draw()
  225. @image_comparison(['titles'])
  226. def test_titles():
  227. # left and right side titles
  228. plt.figure()
  229. ax = plt.subplot(1, 1, 1)
  230. ax.set_title("left title", loc="left")
  231. ax.set_title("right title", loc="right")
  232. ax.set_xticks([])
  233. ax.set_yticks([])
  234. @image_comparison(['text_alignment'], style='mpl20')
  235. def test_alignment():
  236. plt.figure()
  237. ax = plt.subplot(1, 1, 1)
  238. x = 0.1
  239. for rotation in (0, 30):
  240. for alignment in ('top', 'bottom', 'baseline', 'center'):
  241. ax.text(
  242. x, 0.5, alignment + " Tj", va=alignment, rotation=rotation,
  243. bbox=dict(boxstyle='round', facecolor='wheat', alpha=0.5))
  244. ax.text(
  245. x, 1.0, r'$\sum_{i=0}^{j}$', va=alignment, rotation=rotation)
  246. x += 0.1
  247. ax.plot([0, 1], [0.5, 0.5])
  248. ax.plot([0, 1], [1.0, 1.0])
  249. ax.set_xlim(0, 1)
  250. ax.set_ylim(0, 1.5)
  251. ax.set_xticks([])
  252. ax.set_yticks([])
  253. @image_comparison(['axes_titles.png'])
  254. def test_axes_titles():
  255. # Related to issue #3327
  256. plt.figure()
  257. ax = plt.subplot(1, 1, 1)
  258. ax.set_title('center', loc='center', fontsize=20, fontweight=700)
  259. ax.set_title('left', loc='left', fontsize=12, fontweight=400)
  260. ax.set_title('right', loc='right', fontsize=12, fontweight=400)
  261. def test_set_position():
  262. fig, ax = plt.subplots()
  263. # test set_position
  264. ann = ax.annotate(
  265. 'test', (0, 0), xytext=(0, 0), textcoords='figure pixels')
  266. fig.canvas.draw()
  267. init_pos = ann.get_window_extent(fig.canvas.renderer)
  268. shift_val = 15
  269. ann.set_position((shift_val, shift_val))
  270. fig.canvas.draw()
  271. post_pos = ann.get_window_extent(fig.canvas.renderer)
  272. for a, b in zip(init_pos.min, post_pos.min):
  273. assert a + shift_val == b
  274. # test xyann
  275. ann = ax.annotate(
  276. 'test', (0, 0), xytext=(0, 0), textcoords='figure pixels')
  277. fig.canvas.draw()
  278. init_pos = ann.get_window_extent(fig.canvas.renderer)
  279. shift_val = 15
  280. ann.xyann = (shift_val, shift_val)
  281. fig.canvas.draw()
  282. post_pos = ann.get_window_extent(fig.canvas.renderer)
  283. for a, b in zip(init_pos.min, post_pos.min):
  284. assert a + shift_val == b
  285. def test_char_index_at():
  286. fig = plt.figure()
  287. text = fig.text(0.1, 0.9, "")
  288. text.set_text("i")
  289. bbox = text.get_window_extent()
  290. size_i = bbox.x1 - bbox.x0
  291. text.set_text("m")
  292. bbox = text.get_window_extent()
  293. size_m = bbox.x1 - bbox.x0
  294. text.set_text("iiiimmmm")
  295. bbox = text.get_window_extent()
  296. origin = bbox.x0
  297. assert text._char_index_at(origin - size_i) == 0 # left of first char
  298. assert text._char_index_at(origin) == 0
  299. assert text._char_index_at(origin + 0.499*size_i) == 0
  300. assert text._char_index_at(origin + 0.501*size_i) == 1
  301. assert text._char_index_at(origin + size_i*3) == 3
  302. assert text._char_index_at(origin + size_i*4 + size_m*3) == 7
  303. assert text._char_index_at(origin + size_i*4 + size_m*4) == 8
  304. assert text._char_index_at(origin + size_i*4 + size_m*10) == 8
  305. @pytest.mark.parametrize('text', ['', 'O'], ids=['empty', 'non-empty'])
  306. def test_non_default_dpi(text):
  307. fig, ax = plt.subplots()
  308. t1 = ax.text(0.5, 0.5, text, ha='left', va='bottom')
  309. fig.canvas.draw()
  310. dpi = fig.dpi
  311. bbox1 = t1.get_window_extent()
  312. bbox2 = t1.get_window_extent(dpi=dpi * 10)
  313. np.testing.assert_allclose(bbox2.get_points(), bbox1.get_points() * 10,
  314. rtol=5e-2)
  315. # Text.get_window_extent should not permanently change dpi.
  316. assert fig.dpi == dpi
  317. def test_get_rotation_string():
  318. assert Text(rotation='horizontal').get_rotation() == 0.
  319. assert Text(rotation='vertical').get_rotation() == 90.
  320. def test_get_rotation_float():
  321. for i in [15., 16.70, 77.4]:
  322. assert Text(rotation=i).get_rotation() == i
  323. def test_get_rotation_int():
  324. for i in [67, 16, 41]:
  325. assert Text(rotation=i).get_rotation() == float(i)
  326. def test_get_rotation_raises():
  327. with pytest.raises(ValueError):
  328. Text(rotation='hozirontal')
  329. def test_get_rotation_none():
  330. assert Text(rotation=None).get_rotation() == 0.0
  331. def test_get_rotation_mod360():
  332. for i, j in zip([360., 377., 720+177.2], [0., 17., 177.2]):
  333. assert_almost_equal(Text(rotation=i).get_rotation(), j)
  334. @pytest.mark.parametrize("ha", ["center", "right", "left"])
  335. @pytest.mark.parametrize("va", ["center", "top", "bottom",
  336. "baseline", "center_baseline"])
  337. def test_null_rotation_with_rotation_mode(ha, va):
  338. fig, ax = plt.subplots()
  339. kw = dict(rotation=0, va=va, ha=ha)
  340. t0 = ax.text(.5, .5, 'test', rotation_mode='anchor', **kw)
  341. t1 = ax.text(.5, .5, 'test', rotation_mode='default', **kw)
  342. fig.canvas.draw()
  343. assert_almost_equal(t0.get_window_extent(fig.canvas.renderer).get_points(),
  344. t1.get_window_extent(fig.canvas.renderer).get_points())
  345. @image_comparison(['text_bboxclip'])
  346. def test_bbox_clipping():
  347. plt.text(0.9, 0.2, 'Is bbox clipped?', backgroundcolor='r', clip_on=True)
  348. t = plt.text(0.9, 0.5, 'Is fancy bbox clipped?', clip_on=True)
  349. t.set_bbox({"boxstyle": "round, pad=0.1"})
  350. @image_comparison(['annotation_negative_ax_coords.png'])
  351. def test_annotation_negative_ax_coords():
  352. fig, ax = plt.subplots()
  353. ax.annotate('+ pts',
  354. xytext=[30, 20], textcoords='axes points',
  355. xy=[30, 20], xycoords='axes points', fontsize=32)
  356. ax.annotate('- pts',
  357. xytext=[30, -20], textcoords='axes points',
  358. xy=[30, -20], xycoords='axes points', fontsize=32,
  359. va='top')
  360. ax.annotate('+ frac',
  361. xytext=[0.75, 0.05], textcoords='axes fraction',
  362. xy=[0.75, 0.05], xycoords='axes fraction', fontsize=32)
  363. ax.annotate('- frac',
  364. xytext=[0.75, -0.05], textcoords='axes fraction',
  365. xy=[0.75, -0.05], xycoords='axes fraction', fontsize=32,
  366. va='top')
  367. ax.annotate('+ pixels',
  368. xytext=[160, 25], textcoords='axes pixels',
  369. xy=[160, 25], xycoords='axes pixels', fontsize=32)
  370. ax.annotate('- pixels',
  371. xytext=[160, -25], textcoords='axes pixels',
  372. xy=[160, -25], xycoords='axes pixels', fontsize=32,
  373. va='top')
  374. @image_comparison(['annotation_negative_fig_coords.png'])
  375. def test_annotation_negative_fig_coords():
  376. fig, ax = plt.subplots()
  377. ax.annotate('+ pts',
  378. xytext=[10, 120], textcoords='figure points',
  379. xy=[10, 120], xycoords='figure points', fontsize=32)
  380. ax.annotate('- pts',
  381. xytext=[-10, 180], textcoords='figure points',
  382. xy=[-10, 180], xycoords='figure points', fontsize=32,
  383. va='top')
  384. ax.annotate('+ frac',
  385. xytext=[0.05, 0.55], textcoords='figure fraction',
  386. xy=[0.05, 0.55], xycoords='figure fraction', fontsize=32)
  387. ax.annotate('- frac',
  388. xytext=[-0.05, 0.5], textcoords='figure fraction',
  389. xy=[-0.05, 0.5], xycoords='figure fraction', fontsize=32,
  390. va='top')
  391. ax.annotate('+ pixels',
  392. xytext=[50, 50], textcoords='figure pixels',
  393. xy=[50, 50], xycoords='figure pixels', fontsize=32)
  394. ax.annotate('- pixels',
  395. xytext=[-50, 100], textcoords='figure pixels',
  396. xy=[-50, 100], xycoords='figure pixels', fontsize=32,
  397. va='top')
  398. def test_text_stale():
  399. fig, (ax1, ax2) = plt.subplots(1, 2)
  400. plt.draw_all()
  401. assert not ax1.stale
  402. assert not ax2.stale
  403. assert not fig.stale
  404. txt1 = ax1.text(.5, .5, 'aardvark')
  405. assert ax1.stale
  406. assert txt1.stale
  407. assert fig.stale
  408. ann1 = ax2.annotate('aardvark', xy=[.5, .5])
  409. assert ax2.stale
  410. assert ann1.stale
  411. assert fig.stale
  412. plt.draw_all()
  413. assert not ax1.stale
  414. assert not ax2.stale
  415. assert not fig.stale
  416. @image_comparison(['agg_text_clip.png'])
  417. def test_agg_text_clip():
  418. np.random.seed(1)
  419. fig, (ax1, ax2) = plt.subplots(2)
  420. for x, y in np.random.rand(10, 2):
  421. ax1.text(x, y, "foo", clip_on=True)
  422. ax2.text(x, y, "foo")
  423. def test_text_size_binding():
  424. mpl.rcParams['font.size'] = 10
  425. fp = mpl.font_manager.FontProperties(size='large')
  426. sz1 = fp.get_size_in_points()
  427. mpl.rcParams['font.size'] = 100
  428. assert sz1 == fp.get_size_in_points()
  429. @image_comparison(['font_scaling.pdf'])
  430. def test_font_scaling():
  431. mpl.rcParams['pdf.fonttype'] = 42
  432. fig, ax = plt.subplots(figsize=(6.4, 12.4))
  433. ax.xaxis.set_major_locator(plt.NullLocator())
  434. ax.yaxis.set_major_locator(plt.NullLocator())
  435. ax.set_ylim(-10, 600)
  436. for i, fs in enumerate(range(4, 43, 2)):
  437. ax.text(0.1, i*30, f"{fs} pt font size", fontsize=fs)
  438. @pytest.mark.parametrize('spacing1, spacing2', [(0.4, 2), (2, 0.4), (2, 2)])
  439. def test_two_2line_texts(spacing1, spacing2):
  440. text_string = 'line1\nline2'
  441. fig = plt.figure()
  442. renderer = fig.canvas.get_renderer()
  443. text1 = fig.text(0.25, 0.5, text_string, linespacing=spacing1)
  444. text2 = fig.text(0.25, 0.5, text_string, linespacing=spacing2)
  445. fig.canvas.draw()
  446. box1 = text1.get_window_extent(renderer=renderer)
  447. box2 = text2.get_window_extent(renderer=renderer)
  448. # line spacing only affects height
  449. assert box1.width == box2.width
  450. if spacing1 == spacing2:
  451. assert box1.height == box2.height
  452. else:
  453. assert box1.height != box2.height
  454. def test_validate_linespacing():
  455. with pytest.raises(TypeError):
  456. plt.text(.25, .5, "foo", linespacing="abc")
  457. def test_nonfinite_pos():
  458. fig, ax = plt.subplots()
  459. ax.text(0, np.nan, 'nan')
  460. ax.text(np.inf, 0, 'inf')
  461. fig.canvas.draw()
  462. def test_hinting_factor_backends():
  463. plt.rcParams['text.hinting_factor'] = 1
  464. fig = plt.figure()
  465. t = fig.text(0.5, 0.5, 'some text')
  466. fig.savefig(io.BytesIO(), format='svg')
  467. expected = t.get_window_extent().intervalx
  468. fig.savefig(io.BytesIO(), format='png')
  469. # Backends should apply hinting_factor consistently (within 10%).
  470. np.testing.assert_allclose(t.get_window_extent().intervalx, expected,
  471. rtol=0.1)
  472. @needs_usetex
  473. def test_usetex_is_copied():
  474. # Indirectly tests that update_from (which is used to copy tick label
  475. # properties) copies usetex state.
  476. fig = plt.figure()
  477. plt.rcParams["text.usetex"] = False
  478. ax1 = fig.add_subplot(121)
  479. plt.rcParams["text.usetex"] = True
  480. ax2 = fig.add_subplot(122)
  481. fig.canvas.draw()
  482. for ax, usetex in [(ax1, False), (ax2, True)]:
  483. for t in ax.xaxis.majorTicks:
  484. assert t.label1.get_usetex() == usetex
  485. @needs_usetex
  486. def test_single_artist_usetex():
  487. # Check that a single artist marked with usetex does not get passed through
  488. # the mathtext parser at all (for the Agg backend) (the mathtext parser
  489. # currently fails to parse \frac12, requiring \frac{1}{2} instead).
  490. fig = plt.figure()
  491. fig.text(.5, .5, r"$\frac12$", usetex=True)
  492. fig.canvas.draw()
  493. @pytest.mark.parametrize("fmt", ["png", "pdf", "svg"])
  494. def test_single_artist_usenotex(fmt):
  495. # Check that a single artist can be marked as not-usetex even though the
  496. # rcParam is on ("2_2_2" fails if passed to TeX). This currently skips
  497. # postscript output as the ps renderer doesn't support mixing usetex and
  498. # non-usetex.
  499. plt.rcParams["text.usetex"] = True
  500. fig = plt.figure()
  501. fig.text(.5, .5, "2_2_2", usetex=False)
  502. fig.savefig(io.BytesIO(), format=fmt)
  503. @image_comparison(['text_as_path_opacity.svg'])
  504. def test_text_as_path_opacity():
  505. plt.figure()
  506. plt.gca().set_axis_off()
  507. plt.text(0.25, 0.25, 'c', color=(0, 0, 0, 0.5))
  508. plt.text(0.25, 0.5, 'a', alpha=0.5)
  509. plt.text(0.25, 0.75, 'x', alpha=0.5, color=(0, 0, 0, 1))
  510. @image_comparison(['text_as_text_opacity.svg'])
  511. def test_text_as_text_opacity():
  512. mpl.rcParams['svg.fonttype'] = 'none'
  513. plt.figure()
  514. plt.gca().set_axis_off()
  515. plt.text(0.25, 0.25, '50% using `color`', color=(0, 0, 0, 0.5))
  516. plt.text(0.25, 0.5, '50% using `alpha`', alpha=0.5)
  517. plt.text(0.25, 0.75, '50% using `alpha` and 100% `color`', alpha=0.5,
  518. color=(0, 0, 0, 1))
  519. def test_text_repr():
  520. # smoketest to make sure text repr doesn't error for category
  521. plt.plot(['A', 'B'], [1, 2])
  522. repr(plt.text(['A'], 0.5, 'Boo'))
  523. def test_annotation_update():
  524. fig, ax = plt.subplots(1, 1)
  525. an = ax.annotate('annotation', xy=(0.5, 0.5))
  526. extent1 = an.get_window_extent(fig.canvas.get_renderer())
  527. fig.tight_layout()
  528. extent2 = an.get_window_extent(fig.canvas.get_renderer())
  529. assert not np.allclose(extent1.get_points(), extent2.get_points(),
  530. rtol=1e-6)
  531. @check_figures_equal(extensions=["png"])
  532. def test_annotation_units(fig_test, fig_ref):
  533. ax = fig_test.add_subplot()
  534. ax.plot(datetime.now(), 1, "o") # Implicitly set axes extents.
  535. ax.annotate("x", (datetime.now(), 0.5), xycoords=("data", "axes fraction"),
  536. # This used to crash before.
  537. xytext=(0, 0), textcoords="offset points")
  538. ax = fig_ref.add_subplot()
  539. ax.plot(datetime.now(), 1, "o")
  540. ax.annotate("x", (datetime.now(), 0.5), xycoords=("data", "axes fraction"))
  541. @image_comparison(['large_subscript_title.png'], style='mpl20')
  542. def test_large_subscript_title():
  543. # Remove this line when this test image is regenerated.
  544. plt.rcParams['text.kerning_factor'] = 6
  545. plt.rcParams['axes.titley'] = None
  546. fig, axs = plt.subplots(1, 2, figsize=(9, 2.5), constrained_layout=True)
  547. ax = axs[0]
  548. ax.set_title(r'$\sum_{i} x_i$')
  549. ax.set_title('New way', loc='left')
  550. ax.set_xticklabels([])
  551. ax = axs[1]
  552. ax.set_title(r'$\sum_{i} x_i$', y=1.01)
  553. ax.set_title('Old Way', loc='left')
  554. ax.set_xticklabels([])
  555. @pytest.mark.parametrize(
  556. "x, rotation, halign",
  557. [(0.7, 0, 'left'),
  558. (0.5, 95, 'left'),
  559. (0.3, 0, 'right'),
  560. (0.3, 185, 'left')])
  561. def test_wrap(x, rotation, halign):
  562. fig = plt.figure(figsize=(18, 18))
  563. gs = GridSpec(nrows=3, ncols=3, figure=fig)
  564. subfig = fig.add_subfigure(gs[1, 1])
  565. # we only use the central subfigure, which does not align with any
  566. # figure boundary, to ensure only subfigure boundaries are relevant
  567. s = 'This is a very long text that should be wrapped multiple times.'
  568. text = subfig.text(x, 0.7, s, wrap=True, rotation=rotation, ha=halign)
  569. fig.canvas.draw()
  570. assert text._get_wrapped_text() == ('This is a very long\n'
  571. 'text that should be\n'
  572. 'wrapped multiple\n'
  573. 'times.')
  574. def test_mathwrap():
  575. fig = plt.figure(figsize=(6, 4))
  576. s = r'This is a very $\overline{\mathrm{long}}$ line of Mathtext.'
  577. text = fig.text(0, 0.5, s, size=40, wrap=True)
  578. fig.canvas.draw()
  579. assert text._get_wrapped_text() == ('This is a very $\\overline{\\mathrm{long}}$\n'
  580. 'line of Mathtext.')
  581. def test_get_window_extent_wrapped():
  582. # Test that a long title that wraps to two lines has the same vertical
  583. # extent as an explicit two line title.
  584. fig1 = plt.figure(figsize=(3, 3))
  585. fig1.suptitle("suptitle that is clearly too long in this case", wrap=True)
  586. window_extent_test = fig1._suptitle.get_window_extent()
  587. fig2 = plt.figure(figsize=(3, 3))
  588. fig2.suptitle("suptitle that is clearly\ntoo long in this case")
  589. window_extent_ref = fig2._suptitle.get_window_extent()
  590. assert window_extent_test.y0 == window_extent_ref.y0
  591. assert window_extent_test.y1 == window_extent_ref.y1
  592. def test_long_word_wrap():
  593. fig = plt.figure(figsize=(6, 4))
  594. text = fig.text(9.5, 8, 'Alonglineoftexttowrap', wrap=True)
  595. fig.canvas.draw()
  596. assert text._get_wrapped_text() == 'Alonglineoftexttowrap'
  597. def test_wrap_no_wrap():
  598. fig = plt.figure(figsize=(6, 4))
  599. text = fig.text(0, 0, 'non wrapped text', wrap=True)
  600. fig.canvas.draw()
  601. assert text._get_wrapped_text() == 'non wrapped text'
  602. @check_figures_equal(extensions=["png"])
  603. def test_buffer_size(fig_test, fig_ref):
  604. # On old versions of the Agg renderer, large non-ascii single-character
  605. # strings (here, "€") would be rendered clipped because the rendering
  606. # buffer would be set by the physical size of the smaller "a" character.
  607. ax = fig_test.add_subplot()
  608. ax.set_yticks([0, 1])
  609. ax.set_yticklabels(["€", "a"])
  610. ax.yaxis.majorTicks[1].label1.set_color("w")
  611. ax = fig_ref.add_subplot()
  612. ax.set_yticks([0, 1])
  613. ax.set_yticklabels(["€", ""])
  614. def test_fontproperties_kwarg_precedence():
  615. """Test that kwargs take precedence over fontproperties defaults."""
  616. plt.figure()
  617. text1 = plt.xlabel("value", fontproperties='Times New Roman', size=40.0)
  618. text2 = plt.ylabel("counts", size=40.0, fontproperties='Times New Roman')
  619. assert text1.get_size() == 40.0
  620. assert text2.get_size() == 40.0
  621. def test_transform_rotates_text():
  622. ax = plt.gca()
  623. transform = mtransforms.Affine2D().rotate_deg(30)
  624. text = ax.text(0, 0, 'test', transform=transform,
  625. transform_rotates_text=True)
  626. result = text.get_rotation()
  627. assert_almost_equal(result, 30)
  628. def test_update_mutate_input():
  629. inp = dict(fontproperties=FontProperties(weight="bold"),
  630. bbox=None)
  631. cache = dict(inp)
  632. t = Text()
  633. t.update(inp)
  634. assert inp['fontproperties'] == cache['fontproperties']
  635. assert inp['bbox'] == cache['bbox']
  636. @pytest.mark.parametrize('rotation', ['invalid string', [90]])
  637. def test_invalid_rotation_values(rotation):
  638. with pytest.raises(
  639. ValueError,
  640. match=("rotation must be 'vertical', 'horizontal' or a number")):
  641. Text(0, 0, 'foo', rotation=rotation)
  642. def test_invalid_color():
  643. with pytest.raises(ValueError):
  644. plt.figtext(.5, .5, "foo", c="foobar")
  645. @image_comparison(['text_pdf_kerning.pdf'], style='mpl20')
  646. def test_pdf_kerning():
  647. plt.figure()
  648. plt.figtext(0.1, 0.5, "ATATATATATATATATATA", size=30)
  649. def test_unsupported_script(recwarn):
  650. fig = plt.figure()
  651. t = fig.text(.5, .5, "\N{BENGALI DIGIT ZERO}")
  652. fig.canvas.draw()
  653. assert all(isinstance(warn.message, UserWarning) for warn in recwarn)
  654. assert (
  655. [warn.message.args for warn in recwarn] ==
  656. [(r"Glyph 2534 (\N{BENGALI DIGIT ZERO}) missing from font(s) "
  657. + f"{t.get_fontname()}.",),
  658. (r"Matplotlib currently does not support Bengali natively.",)])
  659. # See gh-26152 for more information on this xfail
  660. @pytest.mark.xfail(pyparsing_version.release == (3, 1, 0),
  661. reason="Error messages are incorrect with pyparsing 3.1.0")
  662. def test_parse_math():
  663. fig, ax = plt.subplots()
  664. ax.text(0, 0, r"$ \wrong{math} $", parse_math=False)
  665. fig.canvas.draw()
  666. ax.text(0, 0, r"$ \wrong{math} $", parse_math=True)
  667. with pytest.raises(ValueError, match='Unknown symbol'):
  668. fig.canvas.draw()
  669. # See gh-26152 for more information on this xfail
  670. @pytest.mark.xfail(pyparsing_version.release == (3, 1, 0),
  671. reason="Error messages are incorrect with pyparsing 3.1.0")
  672. def test_parse_math_rcparams():
  673. # Default is True
  674. fig, ax = plt.subplots()
  675. ax.text(0, 0, r"$ \wrong{math} $")
  676. with pytest.raises(ValueError, match='Unknown symbol'):
  677. fig.canvas.draw()
  678. # Setting rcParams to False
  679. with mpl.rc_context({'text.parse_math': False}):
  680. fig, ax = plt.subplots()
  681. ax.text(0, 0, r"$ \wrong{math} $")
  682. fig.canvas.draw()
  683. @image_comparison(['text_pdf_font42_kerning.pdf'], style='mpl20')
  684. def test_pdf_font42_kerning():
  685. plt.rcParams['pdf.fonttype'] = 42
  686. plt.figure()
  687. plt.figtext(0.1, 0.5, "ATAVATAVATAVATAVATA", size=30)
  688. @image_comparison(['text_pdf_chars_beyond_bmp.pdf'], style='mpl20')
  689. def test_pdf_chars_beyond_bmp():
  690. plt.rcParams['pdf.fonttype'] = 42
  691. plt.rcParams['mathtext.fontset'] = 'stixsans'
  692. plt.figure()
  693. plt.figtext(0.1, 0.5, "Mass $m$ \U00010308", size=30)
  694. @needs_usetex
  695. def test_metrics_cache():
  696. mpl.text._get_text_metrics_with_cache_impl.cache_clear()
  697. fig = plt.figure()
  698. fig.text(.3, .5, "foo\nbar")
  699. fig.text(.3, .5, "foo\nbar", usetex=True)
  700. fig.text(.5, .5, "foo\nbar", usetex=True)
  701. fig.canvas.draw()
  702. renderer = fig._get_renderer()
  703. ys = {} # mapping of strings to where they were drawn in y with draw_tex.
  704. def call(*args, **kwargs):
  705. renderer, x, y, s, *_ = args
  706. ys.setdefault(s, set()).add(y)
  707. renderer.draw_tex = call
  708. fig.canvas.draw()
  709. assert [*ys] == ["foo", "bar"]
  710. # Check that both TeX strings were drawn with the same y-position for both
  711. # single-line substrings. Previously, there used to be an incorrect cache
  712. # collision with the non-TeX string (drawn first here) whose metrics would
  713. # get incorrectly reused by the first TeX string.
  714. assert len(ys["foo"]) == len(ys["bar"]) == 1
  715. info = mpl.text._get_text_metrics_with_cache_impl.cache_info()
  716. # Every string gets a miss for the first layouting (extents), then a hit
  717. # when drawing, but "foo\nbar" gets two hits as it's drawn twice.
  718. assert info.hits > info.misses
  719. def test_annotate_offset_fontsize():
  720. # Test that offset_fontsize parameter works and uses accurate values
  721. fig, ax = plt.subplots()
  722. text_coords = ['offset points', 'offset fontsize']
  723. # 10 points should be equal to 1 fontsize unit at fontsize=10
  724. xy_text = [(10, 10), (1, 1)]
  725. anns = [ax.annotate('test', xy=(0.5, 0.5),
  726. xytext=xy_text[i],
  727. fontsize='10',
  728. xycoords='data',
  729. textcoords=text_coords[i]) for i in range(2)]
  730. points_coords, fontsize_coords = (ann.get_window_extent() for ann in anns)
  731. fig.canvas.draw()
  732. assert str(points_coords) == str(fontsize_coords)
  733. def test_get_set_antialiased():
  734. txt = Text(.5, .5, "foo\nbar")
  735. assert txt._antialiased == mpl.rcParams['text.antialiased']
  736. assert txt.get_antialiased() == mpl.rcParams['text.antialiased']
  737. txt.set_antialiased(True)
  738. assert txt._antialiased is True
  739. assert txt.get_antialiased() == txt._antialiased
  740. txt.set_antialiased(False)
  741. assert txt._antialiased is False
  742. assert txt.get_antialiased() == txt._antialiased
  743. def test_annotation_antialiased():
  744. annot = Annotation("foo\nbar", (.5, .5), antialiased=True)
  745. assert annot._antialiased is True
  746. assert annot.get_antialiased() == annot._antialiased
  747. annot2 = Annotation("foo\nbar", (.5, .5), antialiased=False)
  748. assert annot2._antialiased is False
  749. assert annot2.get_antialiased() == annot2._antialiased
  750. annot3 = Annotation("foo\nbar", (.5, .5), antialiased=False)
  751. annot3.set_antialiased(True)
  752. assert annot3.get_antialiased() is True
  753. assert annot3._antialiased is True
  754. annot4 = Annotation("foo\nbar", (.5, .5))
  755. assert annot4._antialiased == mpl.rcParams['text.antialiased']
  756. @check_figures_equal(extensions=["png"])
  757. def test_annotate_and_offsetfrom_copy_input(fig_test, fig_ref):
  758. # Both approaches place the text (10, 0) pixels away from the center of the line.
  759. ax = fig_test.add_subplot()
  760. l, = ax.plot([0, 2], [0, 2])
  761. of_xy = np.array([.5, .5])
  762. ax.annotate("foo", textcoords=OffsetFrom(l, of_xy), xytext=(10, 0),
  763. xy=(0, 0)) # xy is unused.
  764. of_xy[:] = 1
  765. ax = fig_ref.add_subplot()
  766. l, = ax.plot([0, 2], [0, 2])
  767. an_xy = np.array([.5, .5])
  768. ax.annotate("foo", xy=an_xy, xycoords=l, xytext=(10, 0), textcoords="offset points")
  769. an_xy[:] = 2
  770. @check_figures_equal()
  771. def test_text_antialiased_off_default_vs_manual(fig_test, fig_ref):
  772. fig_test.text(0.5, 0.5, '6 inches x 2 inches',
  773. antialiased=False)
  774. mpl.rcParams['text.antialiased'] = False
  775. fig_ref.text(0.5, 0.5, '6 inches x 2 inches')
  776. @check_figures_equal()
  777. def test_text_antialiased_on_default_vs_manual(fig_test, fig_ref):
  778. fig_test.text(0.5, 0.5, '6 inches x 2 inches', antialiased=True)
  779. mpl.rcParams['text.antialiased'] = True
  780. fig_ref.text(0.5, 0.5, '6 inches x 2 inches')
  781. def test_text_annotation_get_window_extent():
  782. figure = Figure(dpi=100)
  783. renderer = RendererAgg(200, 200, 100)
  784. # Only text annotation
  785. annotation = Annotation('test', xy=(0, 0), xycoords='figure pixels')
  786. annotation.set_figure(figure)
  787. text = Text(text='test', x=0, y=0)
  788. text.set_figure(figure)
  789. bbox = annotation.get_window_extent(renderer=renderer)
  790. text_bbox = text.get_window_extent(renderer=renderer)
  791. assert bbox.width == text_bbox.width
  792. assert bbox.height == text_bbox.height
  793. _, _, d = renderer.get_text_width_height_descent(
  794. 'text', annotation._fontproperties, ismath=False)
  795. _, _, lp_d = renderer.get_text_width_height_descent(
  796. 'lp', annotation._fontproperties, ismath=False)
  797. below_line = max(d, lp_d)
  798. # These numbers are specific to the current implementation of Text
  799. points = bbox.get_points()
  800. assert points[0, 0] == 0.0
  801. assert points[1, 0] == text_bbox.width
  802. assert points[0, 1] == -below_line
  803. assert points[1, 1] == text_bbox.height - below_line
  804. def test_text_with_arrow_annotation_get_window_extent():
  805. headwidth = 21
  806. fig, ax = plt.subplots(dpi=100)
  807. txt = ax.text(s='test', x=0, y=0)
  808. ann = ax.annotate(
  809. 'test',
  810. xy=(0.0, 50.0),
  811. xytext=(50.0, 50.0), xycoords='figure pixels',
  812. arrowprops={
  813. 'facecolor': 'black', 'width': 2,
  814. 'headwidth': headwidth, 'shrink': 0.0})
  815. plt.draw()
  816. renderer = fig.canvas.renderer
  817. # bounding box of text
  818. text_bbox = txt.get_window_extent(renderer=renderer)
  819. # bounding box of annotation (text + arrow)
  820. bbox = ann.get_window_extent(renderer=renderer)
  821. # bounding box of arrow
  822. arrow_bbox = ann.arrow_patch.get_window_extent(renderer)
  823. # bounding box of annotation text
  824. ann_txt_bbox = Text.get_window_extent(ann)
  825. # make sure annotation width is 50 px wider than
  826. # just the text
  827. assert bbox.width == text_bbox.width + 50.0
  828. # make sure the annotation text bounding box is same size
  829. # as the bounding box of the same string as a Text object
  830. assert ann_txt_bbox.height == text_bbox.height
  831. assert ann_txt_bbox.width == text_bbox.width
  832. # compute the expected bounding box of arrow + text
  833. expected_bbox = mtransforms.Bbox.union([ann_txt_bbox, arrow_bbox])
  834. assert_almost_equal(bbox.height, expected_bbox.height)
  835. def test_arrow_annotation_get_window_extent():
  836. dpi = 100
  837. dots_per_point = dpi / 72
  838. figure = Figure(dpi=dpi)
  839. figure.set_figwidth(2.0)
  840. figure.set_figheight(2.0)
  841. renderer = RendererAgg(200, 200, 100)
  842. # Text annotation with arrow; arrow dimensions are in points
  843. annotation = Annotation(
  844. '', xy=(0.0, 50.0), xytext=(50.0, 50.0), xycoords='figure pixels',
  845. arrowprops={
  846. 'facecolor': 'black', 'width': 8, 'headwidth': 10, 'shrink': 0.0})
  847. annotation.set_figure(figure)
  848. annotation.draw(renderer)
  849. bbox = annotation.get_window_extent()
  850. points = bbox.get_points()
  851. assert bbox.width == 50.0
  852. assert_almost_equal(bbox.height, 10.0 * dots_per_point)
  853. assert points[0, 0] == 0.0
  854. assert points[0, 1] == 50.0 - 5 * dots_per_point
  855. def test_empty_annotation_get_window_extent():
  856. figure = Figure(dpi=100)
  857. figure.set_figwidth(2.0)
  858. figure.set_figheight(2.0)
  859. renderer = RendererAgg(200, 200, 100)
  860. # Text annotation with arrow
  861. annotation = Annotation(
  862. '', xy=(0.0, 50.0), xytext=(0.0, 50.0), xycoords='figure pixels')
  863. annotation.set_figure(figure)
  864. annotation.draw(renderer)
  865. bbox = annotation.get_window_extent()
  866. points = bbox.get_points()
  867. assert points[0, 0] == 0.0
  868. assert points[1, 0] == 0.0
  869. assert points[1, 1] == 50.0
  870. assert points[0, 1] == 50.0
  871. @image_comparison(baseline_images=['basictext_wrap'],
  872. extensions=['png'])
  873. def test_basic_wrap():
  874. fig = plt.figure()
  875. plt.axis([0, 10, 0, 10])
  876. t = "This is a really long string that I'd rather have wrapped so that" \
  877. " it doesn't go outside of the figure, but if it's long enough it" \
  878. " will go off the top or bottom!"
  879. plt.text(4, 1, t, ha='left', rotation=15, wrap=True)
  880. plt.text(6, 5, t, ha='left', rotation=15, wrap=True)
  881. plt.text(5, 5, t, ha='right', rotation=-15, wrap=True)
  882. plt.text(5, 10, t, fontsize=18, style='oblique', ha='center',
  883. va='top', wrap=True)
  884. plt.text(3, 4, t, family='serif', style='italic', ha='right', wrap=True)
  885. plt.text(-1, 0, t, ha='left', rotation=-15, wrap=True)
  886. @image_comparison(baseline_images=['fonttext_wrap'],
  887. extensions=['png'])
  888. def test_font_wrap():
  889. fig = plt.figure()
  890. plt.axis([0, 10, 0, 10])
  891. t = "This is a really long string that I'd rather have wrapped so that" \
  892. " it doesn't go outside of the figure, but if it's long enough it" \
  893. " will go off the top or bottom!"
  894. plt.text(4, -1, t, fontsize=18, family='serif', ha='left', rotation=15,
  895. wrap=True)
  896. plt.text(6, 5, t, family='sans serif', ha='left', rotation=15, wrap=True)
  897. plt.text(5, 10, t, weight='heavy', ha='center', va='top', wrap=True)
  898. plt.text(3, 4, t, family='monospace', ha='right', wrap=True)
  899. plt.text(-1, 0, t, fontsize=14, style='italic', ha='left', rotation=-15,
  900. wrap=True)