test_ticker.py 50 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359
  1. try:
  2. from contextlib import nullcontext
  3. except ImportError:
  4. from contextlib import ExitStack as nullcontext # Py 3.6.
  5. import re
  6. import itertools
  7. import numpy as np
  8. from numpy.testing import assert_almost_equal, assert_array_equal
  9. import pytest
  10. import matplotlib as mpl
  11. from matplotlib import cbook
  12. import matplotlib.pyplot as plt
  13. import matplotlib.ticker as mticker
  14. class TestMaxNLocator:
  15. basic_data = [
  16. (20, 100, np.array([20., 40., 60., 80., 100.])),
  17. (0.001, 0.0001, np.array([0., 0.0002, 0.0004, 0.0006, 0.0008, 0.001])),
  18. (-1e15, 1e15, np.array([-1.0e+15, -5.0e+14, 0e+00, 5e+14, 1.0e+15])),
  19. (0, 0.85e-50, np.arange(6) * 2e-51),
  20. (-0.85e-50, 0, np.arange(-5, 1) * 2e-51),
  21. ]
  22. integer_data = [
  23. (-0.1, 1.1, None, np.array([-1, 0, 1, 2])),
  24. (-0.1, 0.95, None, np.array([-0.25, 0, 0.25, 0.5, 0.75, 1.0])),
  25. (1, 55, [1, 1.5, 5, 6, 10], np.array([0, 15, 30, 45, 60])),
  26. ]
  27. @pytest.mark.parametrize('vmin, vmax, expected', basic_data)
  28. def test_basic(self, vmin, vmax, expected):
  29. loc = mticker.MaxNLocator(nbins=5)
  30. assert_almost_equal(loc.tick_values(vmin, vmax), expected)
  31. @pytest.mark.parametrize('vmin, vmax, steps, expected', integer_data)
  32. def test_integer(self, vmin, vmax, steps, expected):
  33. loc = mticker.MaxNLocator(nbins=5, integer=True, steps=steps)
  34. assert_almost_equal(loc.tick_values(vmin, vmax), expected)
  35. class TestLinearLocator:
  36. def test_basic(self):
  37. loc = mticker.LinearLocator(numticks=3)
  38. test_value = np.array([-0.8, -0.3, 0.2])
  39. assert_almost_equal(loc.tick_values(-0.8, 0.2), test_value)
  40. def test_set_params(self):
  41. """
  42. Create linear locator with presets={}, numticks=2 and change it to
  43. something else. See if change was successful. Should not exception.
  44. """
  45. loc = mticker.LinearLocator(numticks=2)
  46. loc.set_params(numticks=8, presets={(0, 1): []})
  47. assert loc.numticks == 8
  48. assert loc.presets == {(0, 1): []}
  49. class TestMultipleLocator:
  50. def test_basic(self):
  51. loc = mticker.MultipleLocator(base=3.147)
  52. test_value = np.array([-9.441, -6.294, -3.147, 0., 3.147, 6.294,
  53. 9.441, 12.588])
  54. assert_almost_equal(loc.tick_values(-7, 10), test_value)
  55. def test_view_limits(self):
  56. """
  57. Test basic behavior of view limits.
  58. """
  59. with mpl.rc_context({'axes.autolimit_mode': 'data'}):
  60. loc = mticker.MultipleLocator(base=3.147)
  61. assert_almost_equal(loc.view_limits(-5, 5), (-5, 5))
  62. def test_view_limits_round_numbers(self):
  63. """
  64. Test that everything works properly with 'round_numbers' for auto
  65. limit.
  66. """
  67. with mpl.rc_context({'axes.autolimit_mode': 'round_numbers'}):
  68. loc = mticker.MultipleLocator(base=3.147)
  69. assert_almost_equal(loc.view_limits(-4, 4), (-6.294, 6.294))
  70. def test_set_params(self):
  71. """
  72. Create multiple locator with 0.7 base, and change it to something else.
  73. See if change was successful.
  74. """
  75. mult = mticker.MultipleLocator(base=0.7)
  76. mult.set_params(base=1.7)
  77. assert mult._edge.step == 1.7
  78. class TestAutoMinorLocator:
  79. def test_basic(self):
  80. fig, ax = plt.subplots()
  81. ax.set_xlim(0, 1.39)
  82. ax.minorticks_on()
  83. test_value = np.array([0.05, 0.1, 0.15, 0.25, 0.3, 0.35, 0.45,
  84. 0.5, 0.55, 0.65, 0.7, 0.75, 0.85, 0.9,
  85. 0.95, 1.05, 1.1, 1.15, 1.25, 1.3, 1.35])
  86. assert_almost_equal(ax.xaxis.get_ticklocs(minor=True), test_value)
  87. # NB: the following values are assuming that *xlim* is [0, 5]
  88. params = [
  89. (0, 0), # no major tick => no minor tick either
  90. (1, 0) # a single major tick => no minor tick
  91. ]
  92. @pytest.mark.parametrize('nb_majorticks, expected_nb_minorticks', params)
  93. def test_low_number_of_majorticks(
  94. self, nb_majorticks, expected_nb_minorticks):
  95. # This test is related to issue #8804
  96. fig, ax = plt.subplots()
  97. xlims = (0, 5) # easier to test the different code paths
  98. ax.set_xlim(*xlims)
  99. ax.set_xticks(np.linspace(xlims[0], xlims[1], nb_majorticks))
  100. ax.minorticks_on()
  101. ax.xaxis.set_minor_locator(mticker.AutoMinorLocator())
  102. assert len(ax.xaxis.get_minorticklocs()) == expected_nb_minorticks
  103. majorstep_minordivisions = [(1, 5),
  104. (2, 4),
  105. (2.5, 5),
  106. (5, 5),
  107. (10, 5)]
  108. # This test is meant to verify the parameterization for
  109. # test_number_of_minor_ticks
  110. def test_using_all_default_major_steps(self):
  111. with mpl.rc_context({'_internal.classic_mode': False}):
  112. majorsteps = [x[0] for x in self.majorstep_minordivisions]
  113. np.testing.assert_allclose(majorsteps,
  114. mticker.AutoLocator()._steps)
  115. @pytest.mark.parametrize('major_step, expected_nb_minordivisions',
  116. majorstep_minordivisions)
  117. def test_number_of_minor_ticks(
  118. self, major_step, expected_nb_minordivisions):
  119. fig, ax = plt.subplots()
  120. xlims = (0, major_step)
  121. ax.set_xlim(*xlims)
  122. ax.set_xticks(xlims)
  123. ax.minorticks_on()
  124. ax.xaxis.set_minor_locator(mticker.AutoMinorLocator())
  125. nb_minor_divisions = len(ax.xaxis.get_minorticklocs()) + 1
  126. assert nb_minor_divisions == expected_nb_minordivisions
  127. limits = [(0, 1.39), (0, 0.139),
  128. (0, 0.11e-19), (0, 0.112e-12),
  129. (-2.0e-07, -3.3e-08), (1.20e-06, 1.42e-06),
  130. (-1.34e-06, -1.44e-06), (-8.76e-07, -1.51e-06)]
  131. reference = [
  132. [0.05, 0.1, 0.15, 0.25, 0.3, 0.35, 0.45, 0.5, 0.55, 0.65, 0.7,
  133. 0.75, 0.85, 0.9, 0.95, 1.05, 1.1, 1.15, 1.25, 1.3, 1.35],
  134. [0.005, 0.01, 0.015, 0.025, 0.03, 0.035, 0.045, 0.05, 0.055, 0.065,
  135. 0.07, 0.075, 0.085, 0.09, 0.095, 0.105, 0.11, 0.115, 0.125, 0.13,
  136. 0.135],
  137. [5.00e-22, 1.00e-21, 1.50e-21, 2.50e-21, 3.00e-21, 3.50e-21, 4.50e-21,
  138. 5.00e-21, 5.50e-21, 6.50e-21, 7.00e-21, 7.50e-21, 8.50e-21, 9.00e-21,
  139. 9.50e-21, 1.05e-20, 1.10e-20],
  140. [5.00e-15, 1.00e-14, 1.50e-14, 2.50e-14, 3.00e-14, 3.50e-14, 4.50e-14,
  141. 5.00e-14, 5.50e-14, 6.50e-14, 7.00e-14, 7.50e-14, 8.50e-14, 9.00e-14,
  142. 9.50e-14, 1.05e-13, 1.10e-13],
  143. [-1.95e-07, -1.90e-07, -1.85e-07, -1.75e-07, -1.70e-07, -1.65e-07,
  144. -1.55e-07, -1.50e-07, -1.45e-07, -1.35e-07, -1.30e-07, -1.25e-07,
  145. -1.15e-07, -1.10e-07, -1.05e-07, -9.50e-08, -9.00e-08, -8.50e-08,
  146. -7.50e-08, -7.00e-08, -6.50e-08, -5.50e-08, -5.00e-08, -4.50e-08,
  147. -3.50e-08],
  148. [1.21e-06, 1.22e-06, 1.23e-06, 1.24e-06, 1.26e-06, 1.27e-06, 1.28e-06,
  149. 1.29e-06, 1.31e-06, 1.32e-06, 1.33e-06, 1.34e-06, 1.36e-06, 1.37e-06,
  150. 1.38e-06, 1.39e-06, 1.41e-06, 1.42e-06],
  151. [-1.435e-06, -1.430e-06, -1.425e-06, -1.415e-06, -1.410e-06,
  152. -1.405e-06, -1.395e-06, -1.390e-06, -1.385e-06, -1.375e-06,
  153. -1.370e-06, -1.365e-06, -1.355e-06, -1.350e-06, -1.345e-06],
  154. [-1.48e-06, -1.46e-06, -1.44e-06, -1.42e-06, -1.38e-06, -1.36e-06,
  155. -1.34e-06, -1.32e-06, -1.28e-06, -1.26e-06, -1.24e-06, -1.22e-06,
  156. -1.18e-06, -1.16e-06, -1.14e-06, -1.12e-06, -1.08e-06, -1.06e-06,
  157. -1.04e-06, -1.02e-06, -9.80e-07, -9.60e-07, -9.40e-07, -9.20e-07,
  158. -8.80e-07]]
  159. additional_data = list(zip(limits, reference))
  160. @pytest.mark.parametrize('lim, ref', additional_data)
  161. def test_additional(self, lim, ref):
  162. fig, ax = plt.subplots()
  163. ax.minorticks_on()
  164. ax.grid(True, 'minor', 'y', linewidth=1)
  165. ax.grid(True, 'major', color='k', linewidth=1)
  166. ax.set_ylim(lim)
  167. assert_almost_equal(ax.yaxis.get_ticklocs(minor=True), ref)
  168. class TestLogLocator:
  169. def test_basic(self):
  170. loc = mticker.LogLocator(numticks=5)
  171. with pytest.raises(ValueError):
  172. loc.tick_values(0, 1000)
  173. test_value = np.array([1.00000000e-05, 1.00000000e-03, 1.00000000e-01,
  174. 1.00000000e+01, 1.00000000e+03, 1.00000000e+05,
  175. 1.00000000e+07, 1.000000000e+09])
  176. assert_almost_equal(loc.tick_values(0.001, 1.1e5), test_value)
  177. loc = mticker.LogLocator(base=2)
  178. test_value = np.array([0.5, 1., 2., 4., 8., 16., 32., 64., 128., 256.])
  179. assert_almost_equal(loc.tick_values(1, 100), test_value)
  180. def test_switch_to_autolocator(self):
  181. loc = mticker.LogLocator(subs="all")
  182. assert_array_equal(loc.tick_values(0.45, 0.55),
  183. [0.44, 0.46, 0.48, 0.5, 0.52, 0.54, 0.56])
  184. # check that we *skip* 1.0, and 10, because this is a minor locator
  185. loc = mticker.LogLocator(subs=np.arange(2, 10))
  186. assert 1.0 not in loc.tick_values(0.9, 20.)
  187. assert 10.0 not in loc.tick_values(0.9, 20.)
  188. def test_set_params(self):
  189. """
  190. Create log locator with default value, base=10.0, subs=[1.0],
  191. numdecs=4, numticks=15 and change it to something else.
  192. See if change was successful. Should not raise exception.
  193. """
  194. loc = mticker.LogLocator()
  195. loc.set_params(numticks=7, numdecs=8, subs=[2.0], base=4)
  196. assert loc.numticks == 7
  197. assert loc.numdecs == 8
  198. assert loc._base == 4
  199. assert list(loc._subs) == [2.0]
  200. class TestNullLocator:
  201. def test_set_params(self):
  202. """
  203. Create null locator, and attempt to call set_params() on it.
  204. Should not exception, and should raise a warning.
  205. """
  206. loc = mticker.NullLocator()
  207. with pytest.warns(UserWarning):
  208. loc.set_params()
  209. class _LogitHelper:
  210. @staticmethod
  211. def isclose(x, y):
  212. return (np.isclose(-np.log(1/x-1), -np.log(1/y-1))
  213. if 0 < x < 1 and 0 < y < 1 else False)
  214. @staticmethod
  215. def assert_almost_equal(x, y):
  216. ax = np.array(x)
  217. ay = np.array(y)
  218. assert np.all(ax > 0) and np.all(ax < 1)
  219. assert np.all(ay > 0) and np.all(ay < 1)
  220. lx = -np.log(1/ax-1)
  221. ly = -np.log(1/ay-1)
  222. assert_almost_equal(lx, ly)
  223. class TestLogitLocator:
  224. ref_basic_limits = [
  225. (5e-2, 1 - 5e-2),
  226. (5e-3, 1 - 5e-3),
  227. (5e-4, 1 - 5e-4),
  228. (5e-5, 1 - 5e-5),
  229. (5e-6, 1 - 5e-6),
  230. (5e-7, 1 - 5e-7),
  231. (5e-8, 1 - 5e-8),
  232. (5e-9, 1 - 5e-9),
  233. ]
  234. ref_basic_major_ticks = [
  235. 1 / (10 ** np.arange(1, 3)),
  236. 1 / (10 ** np.arange(1, 4)),
  237. 1 / (10 ** np.arange(1, 5)),
  238. 1 / (10 ** np.arange(1, 6)),
  239. 1 / (10 ** np.arange(1, 7)),
  240. 1 / (10 ** np.arange(1, 8)),
  241. 1 / (10 ** np.arange(1, 9)),
  242. 1 / (10 ** np.arange(1, 10)),
  243. ]
  244. ref_maxn_limits = [(0.4, 0.6), (5e-2, 2e-1), (1 - 2e-1, 1 - 5e-2)]
  245. @pytest.mark.parametrize(
  246. "lims, expected_low_ticks",
  247. zip(ref_basic_limits, ref_basic_major_ticks),
  248. )
  249. def test_basic_major(self, lims, expected_low_ticks):
  250. """
  251. Create logit locator with huge number of major, and tests ticks.
  252. """
  253. expected_ticks = sorted(
  254. [*expected_low_ticks, 0.5, *(1 - expected_low_ticks)]
  255. )
  256. loc = mticker.LogitLocator(nbins=100)
  257. _LogitHelper.assert_almost_equal(
  258. loc.tick_values(*lims),
  259. expected_ticks
  260. )
  261. @pytest.mark.parametrize("lims", ref_maxn_limits)
  262. def test_maxn_major(self, lims):
  263. """
  264. When the axis is zoomed, the locator must have the same behavior as
  265. MaxNLocator.
  266. """
  267. loc = mticker.LogitLocator(nbins=100)
  268. maxn_loc = mticker.MaxNLocator(nbins=100, steps=[1, 2, 5, 10])
  269. for nbins in (4, 8, 16):
  270. loc.set_params(nbins=nbins)
  271. maxn_loc.set_params(nbins=nbins)
  272. ticks = loc.tick_values(*lims)
  273. maxn_ticks = maxn_loc.tick_values(*lims)
  274. assert ticks.shape == maxn_ticks.shape
  275. assert (ticks == maxn_ticks).all()
  276. @pytest.mark.parametrize("lims", ref_basic_limits + ref_maxn_limits)
  277. def test_nbins_major(self, lims):
  278. """
  279. Assert logit locator for respecting nbins param.
  280. """
  281. basic_needed = int(-np.floor(np.log10(lims[0]))) * 2 + 1
  282. loc = mticker.LogitLocator(nbins=100)
  283. for nbins in range(basic_needed, 2, -1):
  284. loc.set_params(nbins=nbins)
  285. assert len(loc.tick_values(*lims)) <= nbins + 2
  286. @pytest.mark.parametrize(
  287. "lims, expected_low_ticks",
  288. zip(ref_basic_limits, ref_basic_major_ticks),
  289. )
  290. def test_minor(self, lims, expected_low_ticks):
  291. """
  292. In large scale, test the presence of minor,
  293. and assert no minor when major are subsampled.
  294. """
  295. expected_ticks = sorted(
  296. [*expected_low_ticks, 0.5, *(1 - expected_low_ticks)]
  297. )
  298. basic_needed = len(expected_ticks)
  299. loc = mticker.LogitLocator(nbins=100)
  300. minor_loc = mticker.LogitLocator(nbins=100, minor=True)
  301. for nbins in range(basic_needed, 2, -1):
  302. loc.set_params(nbins=nbins)
  303. minor_loc.set_params(nbins=nbins)
  304. major_ticks = loc.tick_values(*lims)
  305. minor_ticks = minor_loc.tick_values(*lims)
  306. if len(major_ticks) >= len(expected_ticks):
  307. # no subsample, we must have a lot of minors ticks
  308. assert (len(major_ticks) - 1) * 5 < len(minor_ticks)
  309. else:
  310. # subsample
  311. _LogitHelper.assert_almost_equal(
  312. sorted([*major_ticks, *minor_ticks]), expected_ticks)
  313. def test_minor_attr(self):
  314. loc = mticker.LogitLocator(nbins=100)
  315. assert not loc.minor
  316. loc.minor = True
  317. assert loc.minor
  318. loc.set_params(minor=False)
  319. assert not loc.minor
  320. acceptable_vmin_vmax = [
  321. *(2.5 ** np.arange(-3, 0)),
  322. *(1 - 2.5 ** np.arange(-3, 0)),
  323. ]
  324. @pytest.mark.parametrize(
  325. "lims",
  326. [
  327. (a, b)
  328. for (a, b) in itertools.product(acceptable_vmin_vmax, repeat=2)
  329. if a != b
  330. ],
  331. )
  332. def test_nonsingular_ok(self, lims):
  333. """
  334. Create logit locator, and test the nonsingular method for acceptable
  335. value
  336. """
  337. loc = mticker.LogitLocator()
  338. lims2 = loc.nonsingular(*lims)
  339. assert sorted(lims) == sorted(lims2)
  340. @pytest.mark.parametrize("okval", acceptable_vmin_vmax)
  341. def test_nonsingular_nok(self, okval):
  342. """
  343. Create logit locator, and test the nonsingular method for non
  344. acceptable value
  345. """
  346. loc = mticker.LogitLocator()
  347. vmin, vmax = (-1, okval)
  348. vmin2, vmax2 = loc.nonsingular(vmin, vmax)
  349. assert vmax2 == vmax
  350. assert 0 < vmin2 < vmax2
  351. vmin, vmax = (okval, 2)
  352. vmin2, vmax2 = loc.nonsingular(vmin, vmax)
  353. assert vmin2 == vmin
  354. assert vmin2 < vmax2 < 1
  355. class TestFixedLocator:
  356. def test_set_params(self):
  357. """
  358. Create fixed locator with 5 nbins, and change it to something else.
  359. See if change was successful.
  360. Should not exception.
  361. """
  362. fixed = mticker.FixedLocator(range(0, 24), nbins=5)
  363. fixed.set_params(nbins=7)
  364. assert fixed.nbins == 7
  365. class TestIndexLocator:
  366. def test_set_params(self):
  367. """
  368. Create index locator with 3 base, 4 offset. and change it to something
  369. else. See if change was successful.
  370. Should not exception.
  371. """
  372. index = mticker.IndexLocator(base=3, offset=4)
  373. index.set_params(base=7, offset=7)
  374. assert index._base == 7
  375. assert index.offset == 7
  376. class TestSymmetricalLogLocator:
  377. def test_set_params(self):
  378. """
  379. Create symmetrical log locator with default subs =[1.0] numticks = 15,
  380. and change it to something else.
  381. See if change was successful.
  382. Should not exception.
  383. """
  384. sym = mticker.SymmetricalLogLocator(base=10, linthresh=1)
  385. sym.set_params(subs=[2.0], numticks=8)
  386. assert sym._subs == [2.0]
  387. assert sym.numticks == 8
  388. class TestIndexFormatter:
  389. @pytest.mark.parametrize('x, label', [(-2, ''),
  390. (-1, 'label0'),
  391. (0, 'label0'),
  392. (0.5, 'label1'),
  393. (1, 'label1'),
  394. (1.5, 'label2'),
  395. (2, 'label2'),
  396. (2.5, '')])
  397. def test_formatting(self, x, label):
  398. with cbook._suppress_matplotlib_deprecation_warning():
  399. formatter = mticker.IndexFormatter(['label0', 'label1', 'label2'])
  400. assert formatter(x) == label
  401. class TestScalarFormatter:
  402. offset_data = [
  403. (123, 189, 0),
  404. (-189, -123, 0),
  405. (12341, 12349, 12340),
  406. (-12349, -12341, -12340),
  407. (99999.5, 100010.5, 100000),
  408. (-100010.5, -99999.5, -100000),
  409. (99990.5, 100000.5, 100000),
  410. (-100000.5, -99990.5, -100000),
  411. (1233999, 1234001, 1234000),
  412. (-1234001, -1233999, -1234000),
  413. (1, 1, 1),
  414. (123, 123, 0),
  415. # Test cases courtesy of @WeatherGod
  416. (.4538, .4578, .45),
  417. (3789.12, 3783.1, 3780),
  418. (45124.3, 45831.75, 45000),
  419. (0.000721, 0.0007243, 0.00072),
  420. (12592.82, 12591.43, 12590),
  421. (9., 12., 0),
  422. (900., 1200., 0),
  423. (1900., 1200., 0),
  424. (0.99, 1.01, 1),
  425. (9.99, 10.01, 10),
  426. (99.99, 100.01, 100),
  427. (5.99, 6.01, 6),
  428. (15.99, 16.01, 16),
  429. (-0.452, 0.492, 0),
  430. (-0.492, 0.492, 0),
  431. (12331.4, 12350.5, 12300),
  432. (-12335.3, 12335.3, 0),
  433. ]
  434. use_offset_data = [True, False]
  435. # (sci_type, scilimits, lim, orderOfMag, fewticks)
  436. scilimits_data = [
  437. (False, (0, 0), (10.0, 20.0), 0, False),
  438. (True, (-2, 2), (-10, 20), 0, False),
  439. (True, (-2, 2), (-20, 10), 0, False),
  440. (True, (-2, 2), (-110, 120), 2, False),
  441. (True, (-2, 2), (-120, 110), 2, False),
  442. (True, (-2, 2), (-.001, 0.002), -3, False),
  443. (True, (-7, 7), (0.18e10, 0.83e10), 9, True),
  444. (True, (0, 0), (-1e5, 1e5), 5, False),
  445. (True, (6, 6), (-1e5, 1e5), 6, False),
  446. ]
  447. cursor_data = [
  448. [0., "0.000"],
  449. [0.0123, "0.012"],
  450. [0.123, "0.123"],
  451. [1.23, "1.230"],
  452. [12.3, "12.300"],
  453. ]
  454. @pytest.mark.parametrize('unicode_minus, result',
  455. [(True, "\N{MINUS SIGN}1"), (False, "-1")])
  456. def test_unicode_minus(self, unicode_minus, result):
  457. mpl.rcParams['axes.unicode_minus'] = unicode_minus
  458. assert (
  459. plt.gca().xaxis.get_major_formatter().format_data_short(-1).strip()
  460. == result)
  461. @pytest.mark.parametrize('left, right, offset', offset_data)
  462. def test_offset_value(self, left, right, offset):
  463. fig, ax = plt.subplots()
  464. formatter = ax.get_xaxis().get_major_formatter()
  465. with (pytest.warns(UserWarning, match='Attempting to set identical')
  466. if left == right else nullcontext()):
  467. ax.set_xlim(left, right)
  468. ax.get_xaxis()._update_ticks()
  469. assert formatter.offset == offset
  470. with (pytest.warns(UserWarning, match='Attempting to set identical')
  471. if left == right else nullcontext()):
  472. ax.set_xlim(right, left)
  473. ax.get_xaxis()._update_ticks()
  474. assert formatter.offset == offset
  475. @pytest.mark.parametrize('use_offset', use_offset_data)
  476. def test_use_offset(self, use_offset):
  477. with mpl.rc_context({'axes.formatter.useoffset': use_offset}):
  478. tmp_form = mticker.ScalarFormatter()
  479. assert use_offset == tmp_form.get_useOffset()
  480. @pytest.mark.parametrize(
  481. 'sci_type, scilimits, lim, orderOfMag, fewticks', scilimits_data)
  482. def test_scilimits(self, sci_type, scilimits, lim, orderOfMag, fewticks):
  483. tmp_form = mticker.ScalarFormatter()
  484. tmp_form.set_scientific(sci_type)
  485. tmp_form.set_powerlimits(scilimits)
  486. fig, ax = plt.subplots()
  487. ax.yaxis.set_major_formatter(tmp_form)
  488. ax.set_ylim(*lim)
  489. if fewticks:
  490. ax.yaxis.set_major_locator(mticker.MaxNLocator(4))
  491. tmp_form.set_locs(ax.yaxis.get_majorticklocs())
  492. assert orderOfMag == tmp_form.orderOfMagnitude
  493. @pytest.mark.parametrize('data, expected', cursor_data)
  494. def test_cursor_precision(self, data, expected):
  495. fig, ax = plt.subplots()
  496. ax.set_xlim(-1, 1) # Pointing precision of 0.001.
  497. fmt = ax.xaxis.get_major_formatter().format_data_short
  498. assert fmt(data) == expected
  499. @pytest.mark.parametrize('data, expected', cursor_data)
  500. def test_cursor_dummy_axis(self, data, expected):
  501. # Issue #17624
  502. sf = mticker.ScalarFormatter()
  503. sf.create_dummy_axis()
  504. sf.set_bounds(0, 10)
  505. fmt = sf.format_data_short
  506. assert fmt(data) == expected
  507. class FakeAxis:
  508. """Allow Formatter to be called without having a "full" plot set up."""
  509. def __init__(self, vmin=1, vmax=10):
  510. self.vmin = vmin
  511. self.vmax = vmax
  512. def get_view_interval(self):
  513. return self.vmin, self.vmax
  514. class TestLogFormatterExponent:
  515. param_data = [
  516. (True, 4, np.arange(-3, 4.0), np.arange(-3, 4.0),
  517. ['-3', '-2', '-1', '0', '1', '2', '3']),
  518. # With labelOnlyBase=False, non-integer powers should be nicely
  519. # formatted.
  520. (False, 10, np.array([0.1, 0.00001, np.pi, 0.2, -0.2, -0.00001]),
  521. range(6), ['0.1', '1e-05', '3.14', '0.2', '-0.2', '-1e-05']),
  522. (False, 50, np.array([3, 5, 12, 42], dtype=float), range(6),
  523. ['3', '5', '12', '42']),
  524. ]
  525. base_data = [2.0, 5.0, 10.0, np.pi, np.e]
  526. @pytest.mark.parametrize(
  527. 'labelOnlyBase, exponent, locs, positions, expected', param_data)
  528. @pytest.mark.parametrize('base', base_data)
  529. def test_basic(self, labelOnlyBase, base, exponent, locs, positions,
  530. expected):
  531. formatter = mticker.LogFormatterExponent(base=base,
  532. labelOnlyBase=labelOnlyBase)
  533. formatter.axis = FakeAxis(1, base**exponent)
  534. vals = base**locs
  535. labels = [formatter(x, pos) for (x, pos) in zip(vals, positions)]
  536. assert labels == expected
  537. def test_blank(self):
  538. # Should be a blank string for non-integer powers if labelOnlyBase=True
  539. formatter = mticker.LogFormatterExponent(base=10, labelOnlyBase=True)
  540. formatter.axis = FakeAxis()
  541. assert formatter(10**0.1) == ''
  542. class TestLogFormatterMathtext:
  543. fmt = mticker.LogFormatterMathtext()
  544. test_data = [
  545. (0, 1, '$\\mathdefault{10^{0}}$'),
  546. (0, 1e-2, '$\\mathdefault{10^{-2}}$'),
  547. (0, 1e2, '$\\mathdefault{10^{2}}$'),
  548. (3, 1, '$\\mathdefault{1}$'),
  549. (3, 1e-2, '$\\mathdefault{0.01}$'),
  550. (3, 1e2, '$\\mathdefault{100}$'),
  551. (3, 1e-3, '$\\mathdefault{10^{-3}}$'),
  552. (3, 1e3, '$\\mathdefault{10^{3}}$'),
  553. ]
  554. @pytest.mark.parametrize('min_exponent, value, expected', test_data)
  555. def test_min_exponent(self, min_exponent, value, expected):
  556. with mpl.rc_context({'axes.formatter.min_exponent': min_exponent}):
  557. assert self.fmt(value) == expected
  558. class TestLogFormatterSciNotation:
  559. test_data = [
  560. (2, 0.03125, '$\\mathdefault{2^{-5}}$'),
  561. (2, 1, '$\\mathdefault{2^{0}}$'),
  562. (2, 32, '$\\mathdefault{2^{5}}$'),
  563. (2, 0.0375, '$\\mathdefault{1.2\\times2^{-5}}$'),
  564. (2, 1.2, '$\\mathdefault{1.2\\times2^{0}}$'),
  565. (2, 38.4, '$\\mathdefault{1.2\\times2^{5}}$'),
  566. (10, -1, '$\\mathdefault{-10^{0}}$'),
  567. (10, 1e-05, '$\\mathdefault{10^{-5}}$'),
  568. (10, 1, '$\\mathdefault{10^{0}}$'),
  569. (10, 100000, '$\\mathdefault{10^{5}}$'),
  570. (10, 2e-05, '$\\mathdefault{2\\times10^{-5}}$'),
  571. (10, 2, '$\\mathdefault{2\\times10^{0}}$'),
  572. (10, 200000, '$\\mathdefault{2\\times10^{5}}$'),
  573. (10, 5e-05, '$\\mathdefault{5\\times10^{-5}}$'),
  574. (10, 5, '$\\mathdefault{5\\times10^{0}}$'),
  575. (10, 500000, '$\\mathdefault{5\\times10^{5}}$'),
  576. ]
  577. @pytest.mark.style('default')
  578. @pytest.mark.parametrize('base, value, expected', test_data)
  579. def test_basic(self, base, value, expected):
  580. formatter = mticker.LogFormatterSciNotation(base=base)
  581. formatter.sublabel = {1, 2, 5, 1.2}
  582. with mpl.rc_context({'text.usetex': False}):
  583. assert formatter(value) == expected
  584. class TestLogFormatter:
  585. pprint_data = [
  586. (3.141592654e-05, 0.001, '3.142e-5'),
  587. (0.0003141592654, 0.001, '3.142e-4'),
  588. (0.003141592654, 0.001, '3.142e-3'),
  589. (0.03141592654, 0.001, '3.142e-2'),
  590. (0.3141592654, 0.001, '3.142e-1'),
  591. (3.141592654, 0.001, '3.142'),
  592. (31.41592654, 0.001, '3.142e1'),
  593. (314.1592654, 0.001, '3.142e2'),
  594. (3141.592654, 0.001, '3.142e3'),
  595. (31415.92654, 0.001, '3.142e4'),
  596. (314159.2654, 0.001, '3.142e5'),
  597. (1e-05, 0.001, '1e-5'),
  598. (0.0001, 0.001, '1e-4'),
  599. (0.001, 0.001, '1e-3'),
  600. (0.01, 0.001, '1e-2'),
  601. (0.1, 0.001, '1e-1'),
  602. (1, 0.001, '1'),
  603. (10, 0.001, '10'),
  604. (100, 0.001, '100'),
  605. (1000, 0.001, '1000'),
  606. (10000, 0.001, '1e4'),
  607. (100000, 0.001, '1e5'),
  608. (3.141592654e-05, 0.015, '0'),
  609. (0.0003141592654, 0.015, '0'),
  610. (0.003141592654, 0.015, '0.003'),
  611. (0.03141592654, 0.015, '0.031'),
  612. (0.3141592654, 0.015, '0.314'),
  613. (3.141592654, 0.015, '3.142'),
  614. (31.41592654, 0.015, '31.416'),
  615. (314.1592654, 0.015, '314.159'),
  616. (3141.592654, 0.015, '3141.593'),
  617. (31415.92654, 0.015, '31415.927'),
  618. (314159.2654, 0.015, '314159.265'),
  619. (1e-05, 0.015, '0'),
  620. (0.0001, 0.015, '0'),
  621. (0.001, 0.015, '0.001'),
  622. (0.01, 0.015, '0.01'),
  623. (0.1, 0.015, '0.1'),
  624. (1, 0.015, '1'),
  625. (10, 0.015, '10'),
  626. (100, 0.015, '100'),
  627. (1000, 0.015, '1000'),
  628. (10000, 0.015, '10000'),
  629. (100000, 0.015, '100000'),
  630. (3.141592654e-05, 0.5, '0'),
  631. (0.0003141592654, 0.5, '0'),
  632. (0.003141592654, 0.5, '0.003'),
  633. (0.03141592654, 0.5, '0.031'),
  634. (0.3141592654, 0.5, '0.314'),
  635. (3.141592654, 0.5, '3.142'),
  636. (31.41592654, 0.5, '31.416'),
  637. (314.1592654, 0.5, '314.159'),
  638. (3141.592654, 0.5, '3141.593'),
  639. (31415.92654, 0.5, '31415.927'),
  640. (314159.2654, 0.5, '314159.265'),
  641. (1e-05, 0.5, '0'),
  642. (0.0001, 0.5, '0'),
  643. (0.001, 0.5, '0.001'),
  644. (0.01, 0.5, '0.01'),
  645. (0.1, 0.5, '0.1'),
  646. (1, 0.5, '1'),
  647. (10, 0.5, '10'),
  648. (100, 0.5, '100'),
  649. (1000, 0.5, '1000'),
  650. (10000, 0.5, '10000'),
  651. (100000, 0.5, '100000'),
  652. (3.141592654e-05, 5, '0'),
  653. (0.0003141592654, 5, '0'),
  654. (0.003141592654, 5, '0'),
  655. (0.03141592654, 5, '0.03'),
  656. (0.3141592654, 5, '0.31'),
  657. (3.141592654, 5, '3.14'),
  658. (31.41592654, 5, '31.42'),
  659. (314.1592654, 5, '314.16'),
  660. (3141.592654, 5, '3141.59'),
  661. (31415.92654, 5, '31415.93'),
  662. (314159.2654, 5, '314159.27'),
  663. (1e-05, 5, '0'),
  664. (0.0001, 5, '0'),
  665. (0.001, 5, '0'),
  666. (0.01, 5, '0.01'),
  667. (0.1, 5, '0.1'),
  668. (1, 5, '1'),
  669. (10, 5, '10'),
  670. (100, 5, '100'),
  671. (1000, 5, '1000'),
  672. (10000, 5, '10000'),
  673. (100000, 5, '100000'),
  674. (3.141592654e-05, 100, '0'),
  675. (0.0003141592654, 100, '0'),
  676. (0.003141592654, 100, '0'),
  677. (0.03141592654, 100, '0'),
  678. (0.3141592654, 100, '0.3'),
  679. (3.141592654, 100, '3.1'),
  680. (31.41592654, 100, '31.4'),
  681. (314.1592654, 100, '314.2'),
  682. (3141.592654, 100, '3141.6'),
  683. (31415.92654, 100, '31415.9'),
  684. (314159.2654, 100, '314159.3'),
  685. (1e-05, 100, '0'),
  686. (0.0001, 100, '0'),
  687. (0.001, 100, '0'),
  688. (0.01, 100, '0'),
  689. (0.1, 100, '0.1'),
  690. (1, 100, '1'),
  691. (10, 100, '10'),
  692. (100, 100, '100'),
  693. (1000, 100, '1000'),
  694. (10000, 100, '10000'),
  695. (100000, 100, '100000'),
  696. (3.141592654e-05, 1000000.0, '3.1e-5'),
  697. (0.0003141592654, 1000000.0, '3.1e-4'),
  698. (0.003141592654, 1000000.0, '3.1e-3'),
  699. (0.03141592654, 1000000.0, '3.1e-2'),
  700. (0.3141592654, 1000000.0, '3.1e-1'),
  701. (3.141592654, 1000000.0, '3.1'),
  702. (31.41592654, 1000000.0, '3.1e1'),
  703. (314.1592654, 1000000.0, '3.1e2'),
  704. (3141.592654, 1000000.0, '3.1e3'),
  705. (31415.92654, 1000000.0, '3.1e4'),
  706. (314159.2654, 1000000.0, '3.1e5'),
  707. (1e-05, 1000000.0, '1e-5'),
  708. (0.0001, 1000000.0, '1e-4'),
  709. (0.001, 1000000.0, '1e-3'),
  710. (0.01, 1000000.0, '1e-2'),
  711. (0.1, 1000000.0, '1e-1'),
  712. (1, 1000000.0, '1'),
  713. (10, 1000000.0, '10'),
  714. (100, 1000000.0, '100'),
  715. (1000, 1000000.0, '1000'),
  716. (10000, 1000000.0, '1e4'),
  717. (100000, 1000000.0, '1e5'),
  718. ]
  719. @pytest.mark.parametrize('value, domain, expected', pprint_data)
  720. def test_pprint(self, value, domain, expected):
  721. fmt = mticker.LogFormatter()
  722. label = fmt._pprint_val(value, domain)
  723. assert label == expected
  724. def _sub_labels(self, axis, subs=()):
  725. """Test whether locator marks subs to be labeled."""
  726. fmt = axis.get_minor_formatter()
  727. minor_tlocs = axis.get_minorticklocs()
  728. fmt.set_locs(minor_tlocs)
  729. coefs = minor_tlocs / 10**(np.floor(np.log10(minor_tlocs)))
  730. label_expected = [round(c) in subs for c in coefs]
  731. label_test = [fmt(x) != '' for x in minor_tlocs]
  732. assert label_test == label_expected
  733. @pytest.mark.style('default')
  734. def test_sublabel(self):
  735. # test label locator
  736. fig, ax = plt.subplots()
  737. ax.set_xscale('log')
  738. ax.xaxis.set_major_locator(mticker.LogLocator(base=10, subs=[]))
  739. ax.xaxis.set_minor_locator(mticker.LogLocator(base=10,
  740. subs=np.arange(2, 10)))
  741. ax.xaxis.set_major_formatter(mticker.LogFormatter(labelOnlyBase=True))
  742. ax.xaxis.set_minor_formatter(mticker.LogFormatter(labelOnlyBase=False))
  743. # axis range above 3 decades, only bases are labeled
  744. ax.set_xlim(1, 1e4)
  745. fmt = ax.xaxis.get_major_formatter()
  746. fmt.set_locs(ax.xaxis.get_majorticklocs())
  747. show_major_labels = [fmt(x) != ''
  748. for x in ax.xaxis.get_majorticklocs()]
  749. assert np.all(show_major_labels)
  750. self._sub_labels(ax.xaxis, subs=[])
  751. # For the next two, if the numdec threshold in LogFormatter.set_locs
  752. # were 3, then the label sub would be 3 for 2-3 decades and (2, 5)
  753. # for 1-2 decades. With a threshold of 1, subs are not labeled.
  754. # axis range at 2 to 3 decades
  755. ax.set_xlim(1, 800)
  756. self._sub_labels(ax.xaxis, subs=[])
  757. # axis range at 1 to 2 decades
  758. ax.set_xlim(1, 80)
  759. self._sub_labels(ax.xaxis, subs=[])
  760. # axis range at 0.4 to 1 decades, label subs 2, 3, 4, 6
  761. ax.set_xlim(1, 8)
  762. self._sub_labels(ax.xaxis, subs=[2, 3, 4, 6])
  763. # axis range at 0 to 0.4 decades, label all
  764. ax.set_xlim(0.5, 0.9)
  765. self._sub_labels(ax.xaxis, subs=np.arange(2, 10, dtype=int))
  766. @pytest.mark.parametrize('val', [1, 10, 100, 1000])
  767. def test_LogFormatter_call(self, val):
  768. # test _num_to_string method used in __call__
  769. temp_lf = mticker.LogFormatter()
  770. temp_lf.axis = FakeAxis()
  771. assert temp_lf(val) == str(val)
  772. class TestLogitFormatter:
  773. @staticmethod
  774. def logit_deformatter(string):
  775. r"""
  776. Parser to convert string as r'$\mathdefault{1.41\cdot10^{-4}}$' in
  777. float 1.41e-4, as '0.5' or as r'$\mathdefault{\frac{1}{2}}$' in float
  778. 0.5,
  779. """
  780. match = re.match(
  781. r"[^\d]*"
  782. r"(?P<comp>1-)?"
  783. r"(?P<mant>\d*\.?\d*)?"
  784. r"(?:\\cdot)?"
  785. r"(?:10\^\{(?P<expo>-?\d*)})?"
  786. r"[^\d]*$",
  787. string,
  788. )
  789. if match:
  790. comp = match["comp"] is not None
  791. mantissa = float(match["mant"]) if match["mant"] else 1
  792. expo = int(match["expo"]) if match["expo"] is not None else 0
  793. value = mantissa * 10 ** expo
  794. if match["mant"] or match["expo"] is not None:
  795. if comp:
  796. return 1 - value
  797. return value
  798. match = re.match(
  799. r"[^\d]*\\frac\{(?P<num>\d+)\}\{(?P<deno>\d+)\}[^\d]*$", string
  800. )
  801. if match:
  802. num, deno = float(match["num"]), float(match["deno"])
  803. return num / deno
  804. raise ValueError("Not formatted by LogitFormatter")
  805. @pytest.mark.parametrize(
  806. "fx, x",
  807. [
  808. (r"STUFF0.41OTHERSTUFF", 0.41),
  809. (r"STUFF1.41\cdot10^{-2}OTHERSTUFF", 1.41e-2),
  810. (r"STUFF1-0.41OTHERSTUFF", 1 - 0.41),
  811. (r"STUFF1-1.41\cdot10^{-2}OTHERSTUFF", 1 - 1.41e-2),
  812. (r"STUFF", None),
  813. (r"STUFF12.4e-3OTHERSTUFF", None),
  814. ],
  815. )
  816. def test_logit_deformater(self, fx, x):
  817. if x is None:
  818. with pytest.raises(ValueError):
  819. TestLogitFormatter.logit_deformatter(fx)
  820. else:
  821. y = TestLogitFormatter.logit_deformatter(fx)
  822. assert _LogitHelper.isclose(x, y)
  823. decade_test = sorted(
  824. [10 ** (-i) for i in range(1, 10)]
  825. + [1 - 10 ** (-i) for i in range(1, 10)]
  826. + [1 / 2]
  827. )
  828. @pytest.mark.parametrize("x", decade_test)
  829. def test_basic(self, x):
  830. """
  831. Test the formatted value correspond to the value for ideal ticks in
  832. logit space.
  833. """
  834. formatter = mticker.LogitFormatter(use_overline=False)
  835. formatter.set_locs(self.decade_test)
  836. s = formatter(x)
  837. x2 = TestLogitFormatter.logit_deformatter(s)
  838. assert _LogitHelper.isclose(x, x2)
  839. @pytest.mark.parametrize("x", (-1, -0.5, -0.1, 1.1, 1.5, 2))
  840. def test_invalid(self, x):
  841. """
  842. Test that invalid value are formatted with empty string without
  843. raising exception.
  844. """
  845. formatter = mticker.LogitFormatter(use_overline=False)
  846. formatter.set_locs(self.decade_test)
  847. s = formatter(x)
  848. assert s == ""
  849. @pytest.mark.parametrize("x", 1 / (1 + np.exp(-np.linspace(-7, 7, 10))))
  850. def test_variablelength(self, x):
  851. """
  852. The format length should change depending on the neighbor labels.
  853. """
  854. formatter = mticker.LogitFormatter(use_overline=False)
  855. for N in (10, 20, 50, 100, 200, 1000, 2000, 5000, 10000):
  856. if x + 1 / N < 1:
  857. formatter.set_locs([x - 1 / N, x, x + 1 / N])
  858. sx = formatter(x)
  859. sx1 = formatter(x + 1 / N)
  860. d = (
  861. TestLogitFormatter.logit_deformatter(sx1)
  862. - TestLogitFormatter.logit_deformatter(sx)
  863. )
  864. assert 0 < d < 2 / N
  865. lims_minor_major = [
  866. (True, (5e-8, 1 - 5e-8), ((25, False), (75, False))),
  867. (True, (5e-5, 1 - 5e-5), ((25, False), (75, True))),
  868. (True, (5e-2, 1 - 5e-2), ((25, True), (75, True))),
  869. (False, (0.75, 0.76, 0.77), ((7, True), (25, True), (75, True))),
  870. ]
  871. @pytest.mark.parametrize("method, lims, cases", lims_minor_major)
  872. def test_minor_vs_major(self, method, lims, cases):
  873. """
  874. Test minor/major displays.
  875. """
  876. if method:
  877. min_loc = mticker.LogitLocator(minor=True)
  878. ticks = min_loc.tick_values(*lims)
  879. else:
  880. ticks = np.array(lims)
  881. min_form = mticker.LogitFormatter(minor=True)
  882. for threshold, has_minor in cases:
  883. min_form.set_minor_threshold(threshold)
  884. formatted = min_form.format_ticks(ticks)
  885. labelled = [f for f in formatted if len(f) > 0]
  886. if has_minor:
  887. assert len(labelled) > 0, (threshold, has_minor)
  888. else:
  889. assert len(labelled) == 0, (threshold, has_minor)
  890. def test_minor_number(self):
  891. """
  892. Test the parameter minor_number
  893. """
  894. min_loc = mticker.LogitLocator(minor=True)
  895. min_form = mticker.LogitFormatter(minor=True)
  896. ticks = min_loc.tick_values(5e-2, 1 - 5e-2)
  897. for minor_number in (2, 4, 8, 16):
  898. min_form.set_minor_number(minor_number)
  899. formatted = min_form.format_ticks(ticks)
  900. labelled = [f for f in formatted if len(f) > 0]
  901. assert len(labelled) == minor_number
  902. def test_use_overline(self):
  903. """
  904. Test the parameter use_overline
  905. """
  906. x = 1 - 1e-2
  907. fx1 = r"$\mathdefault{1-10^{-2}}$"
  908. fx2 = r"$\mathdefault{\overline{10^{-2}}}$"
  909. form = mticker.LogitFormatter(use_overline=False)
  910. assert form(x) == fx1
  911. form.use_overline(True)
  912. assert form(x) == fx2
  913. form.use_overline(False)
  914. assert form(x) == fx1
  915. def test_one_half(self):
  916. """
  917. Test the parameter one_half
  918. """
  919. form = mticker.LogitFormatter()
  920. assert r"\frac{1}{2}" in form(1/2)
  921. form.set_one_half("1/2")
  922. assert "1/2" in form(1/2)
  923. form.set_one_half("one half")
  924. assert "one half" in form(1/2)
  925. @pytest.mark.parametrize("N", (100, 253, 754))
  926. def test_format_data_short(self, N):
  927. locs = np.linspace(0, 1, N)[1:-1]
  928. form = mticker.LogitFormatter()
  929. for x in locs:
  930. fx = form.format_data_short(x)
  931. if fx.startswith("1-"):
  932. x2 = 1 - float(fx[2:])
  933. else:
  934. x2 = float(fx)
  935. assert abs(x - x2) < 1 / N
  936. class TestFormatStrFormatter:
  937. def test_basic(self):
  938. # test % style formatter
  939. tmp_form = mticker.FormatStrFormatter('%05d')
  940. assert '00002' == tmp_form(2)
  941. class TestStrMethodFormatter:
  942. test_data = [
  943. ('{x:05d}', (2,), '00002'),
  944. ('{x:03d}-{pos:02d}', (2, 1), '002-01'),
  945. ]
  946. @pytest.mark.parametrize('format, input, expected', test_data)
  947. def test_basic(self, format, input, expected):
  948. fmt = mticker.StrMethodFormatter(format)
  949. assert fmt(*input) == expected
  950. class TestEngFormatter:
  951. # (unicode_minus, input, expected) where ''expected'' corresponds to the
  952. # outputs respectively returned when (places=None, places=0, places=2)
  953. # unicode_minus is a boolean value for the rcParam['axes.unicode_minus']
  954. raw_format_data = [
  955. (False, -1234.56789, ('-1.23457 k', '-1 k', '-1.23 k')),
  956. (True, -1234.56789, ('\N{MINUS SIGN}1.23457 k', '\N{MINUS SIGN}1 k',
  957. '\N{MINUS SIGN}1.23 k')),
  958. (False, -1.23456789, ('-1.23457', '-1', '-1.23')),
  959. (True, -1.23456789, ('\N{MINUS SIGN}1.23457', '\N{MINUS SIGN}1',
  960. '\N{MINUS SIGN}1.23')),
  961. (False, -0.123456789, ('-123.457 m', '-123 m', '-123.46 m')),
  962. (True, -0.123456789, ('\N{MINUS SIGN}123.457 m', '\N{MINUS SIGN}123 m',
  963. '\N{MINUS SIGN}123.46 m')),
  964. (False, -0.00123456789, ('-1.23457 m', '-1 m', '-1.23 m')),
  965. (True, -0.00123456789, ('\N{MINUS SIGN}1.23457 m', '\N{MINUS SIGN}1 m',
  966. '\N{MINUS SIGN}1.23 m')),
  967. (True, -0.0, ('0', '0', '0.00')),
  968. (True, -0, ('0', '0', '0.00')),
  969. (True, 0, ('0', '0', '0.00')),
  970. (True, 1.23456789e-6, ('1.23457 µ', '1 µ', '1.23 µ')),
  971. (True, 0.123456789, ('123.457 m', '123 m', '123.46 m')),
  972. (True, 0.1, ('100 m', '100 m', '100.00 m')),
  973. (True, 1, ('1', '1', '1.00')),
  974. (True, 1.23456789, ('1.23457', '1', '1.23')),
  975. # places=0: corner-case rounding
  976. (True, 999.9, ('999.9', '1 k', '999.90')),
  977. # corner-case rounding for all
  978. (True, 999.9999, ('1 k', '1 k', '1.00 k')),
  979. # negative corner-case
  980. (False, -999.9999, ('-1 k', '-1 k', '-1.00 k')),
  981. (True, -999.9999, ('\N{MINUS SIGN}1 k', '\N{MINUS SIGN}1 k',
  982. '\N{MINUS SIGN}1.00 k')),
  983. (True, 1000, ('1 k', '1 k', '1.00 k')),
  984. (True, 1001, ('1.001 k', '1 k', '1.00 k')),
  985. (True, 100001, ('100.001 k', '100 k', '100.00 k')),
  986. (True, 987654.321, ('987.654 k', '988 k', '987.65 k')),
  987. # OoR value (> 1000 Y)
  988. (True, 1.23e27, ('1230 Y', '1230 Y', '1230.00 Y'))
  989. ]
  990. @pytest.mark.parametrize('unicode_minus, input, expected', raw_format_data)
  991. def test_params(self, unicode_minus, input, expected):
  992. """
  993. Test the formatting of EngFormatter for various values of the 'places'
  994. argument, in several cases:
  995. 0. without a unit symbol but with a (default) space separator;
  996. 1. with both a unit symbol and a (default) space separator;
  997. 2. with both a unit symbol and some non default separators;
  998. 3. without a unit symbol but with some non default separators.
  999. Note that cases 2. and 3. are looped over several separator strings.
  1000. """
  1001. plt.rcParams['axes.unicode_minus'] = unicode_minus
  1002. UNIT = 's' # seconds
  1003. DIGITS = '0123456789' # %timeit showed 10-20% faster search than set
  1004. # Case 0: unit='' (default) and sep=' ' (default).
  1005. # 'expected' already corresponds to this reference case.
  1006. exp_outputs = expected
  1007. formatters = (
  1008. mticker.EngFormatter(), # places=None (default)
  1009. mticker.EngFormatter(places=0),
  1010. mticker.EngFormatter(places=2)
  1011. )
  1012. for _formatter, _exp_output in zip(formatters, exp_outputs):
  1013. assert _formatter(input) == _exp_output
  1014. # Case 1: unit=UNIT and sep=' ' (default).
  1015. # Append a unit symbol to the reference case.
  1016. # Beware of the values in [1, 1000), where there is no prefix!
  1017. exp_outputs = (_s + " " + UNIT if _s[-1] in DIGITS # case w/o prefix
  1018. else _s + UNIT for _s in expected)
  1019. formatters = (
  1020. mticker.EngFormatter(unit=UNIT), # places=None (default)
  1021. mticker.EngFormatter(unit=UNIT, places=0),
  1022. mticker.EngFormatter(unit=UNIT, places=2)
  1023. )
  1024. for _formatter, _exp_output in zip(formatters, exp_outputs):
  1025. assert _formatter(input) == _exp_output
  1026. # Test several non default separators: no separator, a narrow
  1027. # no-break space (unicode character) and an extravagant string.
  1028. for _sep in ("", "\N{NARROW NO-BREAK SPACE}", "@_@"):
  1029. # Case 2: unit=UNIT and sep=_sep.
  1030. # Replace the default space separator from the reference case
  1031. # with the tested one `_sep` and append a unit symbol to it.
  1032. exp_outputs = (_s + _sep + UNIT if _s[-1] in DIGITS # no prefix
  1033. else _s.replace(" ", _sep) + UNIT
  1034. for _s in expected)
  1035. formatters = (
  1036. mticker.EngFormatter(unit=UNIT, sep=_sep), # places=None
  1037. mticker.EngFormatter(unit=UNIT, places=0, sep=_sep),
  1038. mticker.EngFormatter(unit=UNIT, places=2, sep=_sep)
  1039. )
  1040. for _formatter, _exp_output in zip(formatters, exp_outputs):
  1041. assert _formatter(input) == _exp_output
  1042. # Case 3: unit='' (default) and sep=_sep.
  1043. # Replace the default space separator from the reference case
  1044. # with the tested one `_sep`. Reference case is already unitless.
  1045. exp_outputs = (_s.replace(" ", _sep) for _s in expected)
  1046. formatters = (
  1047. mticker.EngFormatter(sep=_sep), # places=None (default)
  1048. mticker.EngFormatter(places=0, sep=_sep),
  1049. mticker.EngFormatter(places=2, sep=_sep)
  1050. )
  1051. for _formatter, _exp_output in zip(formatters, exp_outputs):
  1052. assert _formatter(input) == _exp_output
  1053. def test_engformatter_usetex_useMathText():
  1054. fig, ax = plt.subplots()
  1055. ax.plot([0, 500, 1000], [0, 500, 1000])
  1056. ax.set_xticks([0, 500, 1000])
  1057. for formatter in (mticker.EngFormatter(usetex=True),
  1058. mticker.EngFormatter(useMathText=True)):
  1059. ax.xaxis.set_major_formatter(formatter)
  1060. fig.canvas.draw()
  1061. x_tick_label_text = [labl.get_text() for labl in ax.get_xticklabels()]
  1062. # Checking if the dollar `$` signs have been inserted around numbers
  1063. # in tick labels.
  1064. assert x_tick_label_text == ['$0$', '$500$', '$1$ k']
  1065. class TestPercentFormatter:
  1066. percent_data = [
  1067. # Check explicitly set decimals over different intervals and values
  1068. (100, 0, '%', 120, 100, '120%'),
  1069. (100, 0, '%', 100, 90, '100%'),
  1070. (100, 0, '%', 90, 50, '90%'),
  1071. (100, 0, '%', -1.7, 40, '-2%'),
  1072. (100, 1, '%', 90.0, 100, '90.0%'),
  1073. (100, 1, '%', 80.1, 90, '80.1%'),
  1074. (100, 1, '%', 70.23, 50, '70.2%'),
  1075. # 60.554 instead of 60.55: see https://bugs.python.org/issue5118
  1076. (100, 1, '%', -60.554, 40, '-60.6%'),
  1077. # Check auto decimals over different intervals and values
  1078. (100, None, '%', 95, 1, '95.00%'),
  1079. (1.0, None, '%', 3, 6, '300%'),
  1080. (17.0, None, '%', 1, 8.5, '6%'),
  1081. (17.0, None, '%', 1, 8.4, '5.9%'),
  1082. (5, None, '%', -100, 0.000001, '-2000.00000%'),
  1083. # Check percent symbol
  1084. (1.0, 2, None, 1.2, 100, '120.00'),
  1085. (75, 3, '', 50, 100, '66.667'),
  1086. (42, None, '^^Foobar$$', 21, 12, '50.0^^Foobar$$'),
  1087. ]
  1088. percent_ids = [
  1089. # Check explicitly set decimals over different intervals and values
  1090. 'decimals=0, x>100%',
  1091. 'decimals=0, x=100%',
  1092. 'decimals=0, x<100%',
  1093. 'decimals=0, x<0%',
  1094. 'decimals=1, x>100%',
  1095. 'decimals=1, x=100%',
  1096. 'decimals=1, x<100%',
  1097. 'decimals=1, x<0%',
  1098. # Check auto decimals over different intervals and values
  1099. 'autodecimal, x<100%, display_range=1',
  1100. 'autodecimal, x>100%, display_range=6 (custom xmax test)',
  1101. 'autodecimal, x<100%, display_range=8.5 (autodecimal test 1)',
  1102. 'autodecimal, x<100%, display_range=8.4 (autodecimal test 2)',
  1103. 'autodecimal, x<-100%, display_range=1e-6 (tiny display range)',
  1104. # Check percent symbol
  1105. 'None as percent symbol',
  1106. 'Empty percent symbol',
  1107. 'Custom percent symbol',
  1108. ]
  1109. latex_data = [
  1110. (False, False, r'50\{t}%'),
  1111. (False, True, r'50\\\{t\}\%'),
  1112. (True, False, r'50\{t}%'),
  1113. (True, True, r'50\{t}%'),
  1114. ]
  1115. @pytest.mark.parametrize(
  1116. 'xmax, decimals, symbol, x, display_range, expected',
  1117. percent_data, ids=percent_ids)
  1118. def test_basic(self, xmax, decimals, symbol,
  1119. x, display_range, expected):
  1120. formatter = mticker.PercentFormatter(xmax, decimals, symbol)
  1121. with mpl.rc_context(rc={'text.usetex': False}):
  1122. assert formatter.format_pct(x, display_range) == expected
  1123. @pytest.mark.parametrize('is_latex, usetex, expected', latex_data)
  1124. def test_latex(self, is_latex, usetex, expected):
  1125. fmt = mticker.PercentFormatter(symbol='\\{t}%', is_latex=is_latex)
  1126. with mpl.rc_context(rc={'text.usetex': usetex}):
  1127. assert fmt.format_pct(50, 100) == expected
  1128. def test_majformatter_type():
  1129. fig, ax = plt.subplots()
  1130. with pytest.raises(TypeError):
  1131. ax.xaxis.set_major_formatter(mticker.LogLocator())
  1132. def test_minformatter_type():
  1133. fig, ax = plt.subplots()
  1134. with pytest.raises(TypeError):
  1135. ax.xaxis.set_minor_formatter(mticker.LogLocator())
  1136. def test_majlocator_type():
  1137. fig, ax = plt.subplots()
  1138. with pytest.raises(TypeError):
  1139. ax.xaxis.set_major_locator(mticker.LogFormatter())
  1140. def test_minlocator_type():
  1141. fig, ax = plt.subplots()
  1142. with pytest.raises(TypeError):
  1143. ax.xaxis.set_minor_locator(mticker.LogFormatter())
  1144. def test_minorticks_rc():
  1145. fig = plt.figure()
  1146. def minorticksubplot(xminor, yminor, i):
  1147. rc = {'xtick.minor.visible': xminor,
  1148. 'ytick.minor.visible': yminor}
  1149. with plt.rc_context(rc=rc):
  1150. ax = fig.add_subplot(2, 2, i)
  1151. assert (len(ax.xaxis.get_minor_ticks()) > 0) == xminor
  1152. assert (len(ax.yaxis.get_minor_ticks()) > 0) == yminor
  1153. minorticksubplot(False, False, 1)
  1154. minorticksubplot(True, False, 2)
  1155. minorticksubplot(False, True, 3)
  1156. minorticksubplot(True, True, 4)
  1157. @pytest.mark.parametrize('remove_overlapping_locs, expected_num',
  1158. ((True, 6),
  1159. (None, 6), # this tests the default
  1160. (False, 9)))
  1161. def test_remove_overlap(remove_overlapping_locs, expected_num):
  1162. t = np.arange("2018-11-03", "2018-11-06", dtype="datetime64")
  1163. x = np.ones(len(t))
  1164. fig, ax = plt.subplots()
  1165. ax.plot(t, x)
  1166. ax.xaxis.set_major_locator(mpl.dates.DayLocator())
  1167. ax.xaxis.set_major_formatter(mpl.dates.DateFormatter('\n%a'))
  1168. ax.xaxis.set_minor_locator(mpl.dates.HourLocator((0, 6, 12, 18)))
  1169. ax.xaxis.set_minor_formatter(mpl.dates.DateFormatter('%H:%M'))
  1170. # force there to be extra ticks
  1171. ax.xaxis.get_minor_ticks(15)
  1172. if remove_overlapping_locs is not None:
  1173. ax.xaxis.remove_overlapping_locs = remove_overlapping_locs
  1174. # check that getter/setter exists
  1175. current = ax.xaxis.remove_overlapping_locs
  1176. assert (current == ax.xaxis.get_remove_overlapping_locs())
  1177. plt.setp(ax.xaxis, remove_overlapping_locs=current)
  1178. new = ax.xaxis.remove_overlapping_locs
  1179. assert (new == ax.xaxis.remove_overlapping_locs)
  1180. # check that the accessors filter correctly
  1181. # this is the method that does the actual filtering
  1182. assert len(ax.xaxis.get_minorticklocs()) == expected_num
  1183. # these three are derivative
  1184. assert len(ax.xaxis.get_minor_ticks()) == expected_num
  1185. assert len(ax.xaxis.get_minorticklabels()) == expected_num
  1186. assert len(ax.xaxis.get_minorticklines()) == expected_num*2
  1187. @pytest.mark.parametrize('sub', [
  1188. ['hi', 'aardvark'],
  1189. np.zeros((2, 2))])
  1190. def test_bad_locator_subs(sub):
  1191. ll = mticker.LogLocator()
  1192. with pytest.raises(ValueError):
  1193. ll.subs(sub)
  1194. @pytest.mark.parametrize('numticks', [1, 2, 3, 9])
  1195. @pytest.mark.style('default')
  1196. def test_small_range_loglocator(numticks):
  1197. ll = mticker.LogLocator()
  1198. ll.set_params(numticks=numticks)
  1199. for top in [5, 7, 9, 11, 15, 50, 100, 1000]:
  1200. ticks = ll.tick_values(.5, top)
  1201. assert (np.diff(np.log10(ll.tick_values(6, 150))) == 1).all()