test_ticker.py 73 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939
  1. from contextlib import nullcontext
  2. import itertools
  3. import locale
  4. import logging
  5. import re
  6. from packaging.version import parse as parse_version
  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. import matplotlib.pyplot as plt
  12. import matplotlib.ticker as mticker
  13. class TestMaxNLocator:
  14. basic_data = [
  15. (20, 100, np.array([20., 40., 60., 80., 100.])),
  16. (0.001, 0.0001, np.array([0., 0.0002, 0.0004, 0.0006, 0.0008, 0.001])),
  17. (-1e15, 1e15, np.array([-1.0e+15, -5.0e+14, 0e+00, 5e+14, 1.0e+15])),
  18. (0, 0.85e-50, np.arange(6) * 2e-51),
  19. (-0.85e-50, 0, np.arange(-5, 1) * 2e-51),
  20. ]
  21. integer_data = [
  22. (-0.1, 1.1, None, np.array([-1, 0, 1, 2])),
  23. (-0.1, 0.95, None, np.array([-0.25, 0, 0.25, 0.5, 0.75, 1.0])),
  24. (1, 55, [1, 1.5, 5, 6, 10], np.array([0, 15, 30, 45, 60])),
  25. ]
  26. @pytest.mark.parametrize('vmin, vmax, expected', basic_data)
  27. def test_basic(self, vmin, vmax, expected):
  28. loc = mticker.MaxNLocator(nbins=5)
  29. assert_almost_equal(loc.tick_values(vmin, vmax), expected)
  30. @pytest.mark.parametrize('vmin, vmax, steps, expected', integer_data)
  31. def test_integer(self, vmin, vmax, steps, expected):
  32. loc = mticker.MaxNLocator(nbins=5, integer=True, steps=steps)
  33. assert_almost_equal(loc.tick_values(vmin, vmax), expected)
  34. @pytest.mark.parametrize('kwargs, errortype, match', [
  35. ({'foo': 0}, TypeError,
  36. re.escape("set_params() got an unexpected keyword argument 'foo'")),
  37. ({'steps': [2, 1]}, ValueError, "steps argument must be an increasing"),
  38. ({'steps': 2}, ValueError, "steps argument must be an increasing"),
  39. ({'steps': [2, 11]}, ValueError, "steps argument must be an increasing"),
  40. ])
  41. def test_errors(self, kwargs, errortype, match):
  42. with pytest.raises(errortype, match=match):
  43. mticker.MaxNLocator(**kwargs)
  44. @pytest.mark.parametrize('steps, result', [
  45. ([1, 2, 10], [1, 2, 10]),
  46. ([2, 10], [1, 2, 10]),
  47. ([1, 2], [1, 2, 10]),
  48. ([2], [1, 2, 10]),
  49. ])
  50. def test_padding(self, steps, result):
  51. loc = mticker.MaxNLocator(steps=steps)
  52. assert (loc._steps == result).all()
  53. class TestLinearLocator:
  54. def test_basic(self):
  55. loc = mticker.LinearLocator(numticks=3)
  56. test_value = np.array([-0.8, -0.3, 0.2])
  57. assert_almost_equal(loc.tick_values(-0.8, 0.2), test_value)
  58. def test_zero_numticks(self):
  59. loc = mticker.LinearLocator(numticks=0)
  60. loc.tick_values(-0.8, 0.2) == []
  61. def test_set_params(self):
  62. """
  63. Create linear locator with presets={}, numticks=2 and change it to
  64. something else. See if change was successful. Should not exception.
  65. """
  66. loc = mticker.LinearLocator(numticks=2)
  67. loc.set_params(numticks=8, presets={(0, 1): []})
  68. assert loc.numticks == 8
  69. assert loc.presets == {(0, 1): []}
  70. def test_presets(self):
  71. loc = mticker.LinearLocator(presets={(1, 2): [1, 1.25, 1.75],
  72. (0, 2): [0.5, 1.5]})
  73. assert loc.tick_values(1, 2) == [1, 1.25, 1.75]
  74. assert loc.tick_values(2, 1) == [1, 1.25, 1.75]
  75. assert loc.tick_values(0, 2) == [0.5, 1.5]
  76. assert loc.tick_values(0.0, 2.0) == [0.5, 1.5]
  77. assert (loc.tick_values(0, 1) == np.linspace(0, 1, 11)).all()
  78. class TestMultipleLocator:
  79. def test_basic(self):
  80. loc = mticker.MultipleLocator(base=3.147)
  81. test_value = np.array([-9.441, -6.294, -3.147, 0., 3.147, 6.294,
  82. 9.441, 12.588])
  83. assert_almost_equal(loc.tick_values(-7, 10), test_value)
  84. def test_basic_with_offset(self):
  85. loc = mticker.MultipleLocator(base=3.147, offset=1.2)
  86. test_value = np.array([-8.241, -5.094, -1.947, 1.2, 4.347, 7.494,
  87. 10.641])
  88. assert_almost_equal(loc.tick_values(-7, 10), test_value)
  89. def test_view_limits(self):
  90. """
  91. Test basic behavior of view limits.
  92. """
  93. with mpl.rc_context({'axes.autolimit_mode': 'data'}):
  94. loc = mticker.MultipleLocator(base=3.147)
  95. assert_almost_equal(loc.view_limits(-5, 5), (-5, 5))
  96. def test_view_limits_round_numbers(self):
  97. """
  98. Test that everything works properly with 'round_numbers' for auto
  99. limit.
  100. """
  101. with mpl.rc_context({'axes.autolimit_mode': 'round_numbers'}):
  102. loc = mticker.MultipleLocator(base=3.147)
  103. assert_almost_equal(loc.view_limits(-4, 4), (-6.294, 6.294))
  104. def test_view_limits_round_numbers_with_offset(self):
  105. """
  106. Test that everything works properly with 'round_numbers' for auto
  107. limit.
  108. """
  109. with mpl.rc_context({'axes.autolimit_mode': 'round_numbers'}):
  110. loc = mticker.MultipleLocator(base=3.147, offset=1.3)
  111. assert_almost_equal(loc.view_limits(-4, 4), (-4.994, 4.447))
  112. def test_view_limits_single_bin(self):
  113. """
  114. Test that 'round_numbers' works properly with a single bin.
  115. """
  116. with mpl.rc_context({'axes.autolimit_mode': 'round_numbers'}):
  117. loc = mticker.MaxNLocator(nbins=1)
  118. assert_almost_equal(loc.view_limits(-2.3, 2.3), (-4, 4))
  119. def test_set_params(self):
  120. """
  121. Create multiple locator with 0.7 base, and change it to something else.
  122. See if change was successful.
  123. """
  124. mult = mticker.MultipleLocator(base=0.7)
  125. mult.set_params(base=1.7)
  126. assert mult._edge.step == 1.7
  127. mult.set_params(offset=3)
  128. assert mult._offset == 3
  129. class TestAutoMinorLocator:
  130. def test_basic(self):
  131. fig, ax = plt.subplots()
  132. ax.set_xlim(0, 1.39)
  133. ax.minorticks_on()
  134. test_value = np.array([0.05, 0.1, 0.15, 0.25, 0.3, 0.35, 0.45,
  135. 0.5, 0.55, 0.65, 0.7, 0.75, 0.85, 0.9,
  136. 0.95, 1.05, 1.1, 1.15, 1.25, 1.3, 1.35])
  137. assert_almost_equal(ax.xaxis.get_ticklocs(minor=True), test_value)
  138. # NB: the following values are assuming that *xlim* is [0, 5]
  139. params = [
  140. (0, 0), # no major tick => no minor tick either
  141. (1, 0) # a single major tick => no minor tick
  142. ]
  143. def test_first_and_last_minorticks(self):
  144. """
  145. Test that first and last minor tick appear as expected.
  146. """
  147. # This test is related to issue #22331
  148. fig, ax = plt.subplots()
  149. ax.set_xlim(-1.9, 1.9)
  150. ax.xaxis.set_minor_locator(mticker.AutoMinorLocator())
  151. test_value = np.array([-1.9, -1.8, -1.7, -1.6, -1.4, -1.3, -1.2, -1.1,
  152. -0.9, -0.8, -0.7, -0.6, -0.4, -0.3, -0.2, -0.1,
  153. 0.1, 0.2, 0.3, 0.4, 0.6, 0.7, 0.8, 0.9, 1.1,
  154. 1.2, 1.3, 1.4, 1.6, 1.7, 1.8, 1.9])
  155. assert_almost_equal(ax.xaxis.get_ticklocs(minor=True), test_value)
  156. ax.set_xlim(-5, 5)
  157. test_value = np.array([-5.0, -4.5, -3.5, -3.0, -2.5, -1.5, -1.0, -0.5,
  158. 0.5, 1.0, 1.5, 2.5, 3.0, 3.5, 4.5, 5.0])
  159. assert_almost_equal(ax.xaxis.get_ticklocs(minor=True), test_value)
  160. @pytest.mark.parametrize('nb_majorticks, expected_nb_minorticks', params)
  161. def test_low_number_of_majorticks(
  162. self, nb_majorticks, expected_nb_minorticks):
  163. # This test is related to issue #8804
  164. fig, ax = plt.subplots()
  165. xlims = (0, 5) # easier to test the different code paths
  166. ax.set_xlim(*xlims)
  167. ax.set_xticks(np.linspace(xlims[0], xlims[1], nb_majorticks))
  168. ax.minorticks_on()
  169. ax.xaxis.set_minor_locator(mticker.AutoMinorLocator())
  170. assert len(ax.xaxis.get_minorticklocs()) == expected_nb_minorticks
  171. majorstep_minordivisions = [(1, 5),
  172. (2, 4),
  173. (2.5, 5),
  174. (5, 5),
  175. (10, 5)]
  176. # This test is meant to verify the parameterization for
  177. # test_number_of_minor_ticks
  178. def test_using_all_default_major_steps(self):
  179. with mpl.rc_context({'_internal.classic_mode': False}):
  180. majorsteps = [x[0] for x in self.majorstep_minordivisions]
  181. np.testing.assert_allclose(majorsteps,
  182. mticker.AutoLocator()._steps)
  183. @pytest.mark.parametrize('major_step, expected_nb_minordivisions',
  184. majorstep_minordivisions)
  185. def test_number_of_minor_ticks(
  186. self, major_step, expected_nb_minordivisions):
  187. fig, ax = plt.subplots()
  188. xlims = (0, major_step)
  189. ax.set_xlim(*xlims)
  190. ax.set_xticks(xlims)
  191. ax.minorticks_on()
  192. ax.xaxis.set_minor_locator(mticker.AutoMinorLocator())
  193. nb_minor_divisions = len(ax.xaxis.get_minorticklocs()) + 1
  194. assert nb_minor_divisions == expected_nb_minordivisions
  195. limits = [(0, 1.39), (0, 0.139),
  196. (0, 0.11e-19), (0, 0.112e-12),
  197. (-2.0e-07, -3.3e-08), (1.20e-06, 1.42e-06),
  198. (-1.34e-06, -1.44e-06), (-8.76e-07, -1.51e-06)]
  199. reference = [
  200. [0.05, 0.1, 0.15, 0.25, 0.3, 0.35, 0.45, 0.5, 0.55, 0.65, 0.7,
  201. 0.75, 0.85, 0.9, 0.95, 1.05, 1.1, 1.15, 1.25, 1.3, 1.35],
  202. [0.005, 0.01, 0.015, 0.025, 0.03, 0.035, 0.045, 0.05, 0.055, 0.065,
  203. 0.07, 0.075, 0.085, 0.09, 0.095, 0.105, 0.11, 0.115, 0.125, 0.13,
  204. 0.135],
  205. [5.00e-22, 1.00e-21, 1.50e-21, 2.50e-21, 3.00e-21, 3.50e-21, 4.50e-21,
  206. 5.00e-21, 5.50e-21, 6.50e-21, 7.00e-21, 7.50e-21, 8.50e-21, 9.00e-21,
  207. 9.50e-21, 1.05e-20, 1.10e-20],
  208. [5.00e-15, 1.00e-14, 1.50e-14, 2.50e-14, 3.00e-14, 3.50e-14, 4.50e-14,
  209. 5.00e-14, 5.50e-14, 6.50e-14, 7.00e-14, 7.50e-14, 8.50e-14, 9.00e-14,
  210. 9.50e-14, 1.05e-13, 1.10e-13],
  211. [-1.95e-07, -1.90e-07, -1.85e-07, -1.75e-07, -1.70e-07, -1.65e-07,
  212. -1.55e-07, -1.50e-07, -1.45e-07, -1.35e-07, -1.30e-07, -1.25e-07,
  213. -1.15e-07, -1.10e-07, -1.05e-07, -9.50e-08, -9.00e-08, -8.50e-08,
  214. -7.50e-08, -7.00e-08, -6.50e-08, -5.50e-08, -5.00e-08, -4.50e-08,
  215. -3.50e-08],
  216. [1.21e-06, 1.22e-06, 1.23e-06, 1.24e-06, 1.26e-06, 1.27e-06, 1.28e-06,
  217. 1.29e-06, 1.31e-06, 1.32e-06, 1.33e-06, 1.34e-06, 1.36e-06, 1.37e-06,
  218. 1.38e-06, 1.39e-06, 1.41e-06, 1.42e-06],
  219. [-1.435e-06, -1.430e-06, -1.425e-06, -1.415e-06, -1.410e-06,
  220. -1.405e-06, -1.395e-06, -1.390e-06, -1.385e-06, -1.375e-06,
  221. -1.370e-06, -1.365e-06, -1.355e-06, -1.350e-06, -1.345e-06],
  222. [-1.48e-06, -1.46e-06, -1.44e-06, -1.42e-06, -1.38e-06, -1.36e-06,
  223. -1.34e-06, -1.32e-06, -1.28e-06, -1.26e-06, -1.24e-06, -1.22e-06,
  224. -1.18e-06, -1.16e-06, -1.14e-06, -1.12e-06, -1.08e-06, -1.06e-06,
  225. -1.04e-06, -1.02e-06, -9.80e-07, -9.60e-07, -9.40e-07, -9.20e-07,
  226. -8.80e-07]]
  227. additional_data = list(zip(limits, reference))
  228. @pytest.mark.parametrize('lim, ref', additional_data)
  229. def test_additional(self, lim, ref):
  230. fig, ax = plt.subplots()
  231. ax.minorticks_on()
  232. ax.grid(True, 'minor', 'y', linewidth=1)
  233. ax.grid(True, 'major', color='k', linewidth=1)
  234. ax.set_ylim(lim)
  235. assert_almost_equal(ax.yaxis.get_ticklocs(minor=True), ref)
  236. @pytest.mark.parametrize('use_rcparam', [False, True])
  237. @pytest.mark.parametrize(
  238. 'lim, ref', [
  239. ((0, 1.39),
  240. [0.05, 0.1, 0.15, 0.25, 0.3, 0.35, 0.45, 0.5, 0.55, 0.65, 0.7,
  241. 0.75, 0.85, 0.9, 0.95, 1.05, 1.1, 1.15, 1.25, 1.3, 1.35]),
  242. ((0, 0.139),
  243. [0.005, 0.01, 0.015, 0.025, 0.03, 0.035, 0.045, 0.05, 0.055,
  244. 0.065, 0.07, 0.075, 0.085, 0.09, 0.095, 0.105, 0.11, 0.115,
  245. 0.125, 0.13, 0.135]),
  246. ])
  247. def test_number_of_minor_ticks_auto(self, lim, ref, use_rcparam):
  248. if use_rcparam:
  249. context = {'xtick.minor.ndivs': 'auto', 'ytick.minor.ndivs': 'auto'}
  250. kwargs = {}
  251. else:
  252. context = {}
  253. kwargs = {'n': 'auto'}
  254. with mpl.rc_context(context):
  255. fig, ax = plt.subplots()
  256. ax.set_xlim(*lim)
  257. ax.set_ylim(*lim)
  258. ax.xaxis.set_minor_locator(mticker.AutoMinorLocator(**kwargs))
  259. ax.yaxis.set_minor_locator(mticker.AutoMinorLocator(**kwargs))
  260. assert_almost_equal(ax.xaxis.get_ticklocs(minor=True), ref)
  261. assert_almost_equal(ax.yaxis.get_ticklocs(minor=True), ref)
  262. @pytest.mark.parametrize('use_rcparam', [False, True])
  263. @pytest.mark.parametrize(
  264. 'n, lim, ref', [
  265. (2, (0, 4), [0.5, 1.5, 2.5, 3.5]),
  266. (4, (0, 2), [0.25, 0.5, 0.75, 1.25, 1.5, 1.75]),
  267. (10, (0, 1), [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9]),
  268. ])
  269. def test_number_of_minor_ticks_int(self, n, lim, ref, use_rcparam):
  270. if use_rcparam:
  271. context = {'xtick.minor.ndivs': n, 'ytick.minor.ndivs': n}
  272. kwargs = {}
  273. else:
  274. context = {}
  275. kwargs = {'n': n}
  276. with mpl.rc_context(context):
  277. fig, ax = plt.subplots()
  278. ax.set_xlim(*lim)
  279. ax.set_ylim(*lim)
  280. ax.xaxis.set_major_locator(mticker.MultipleLocator(1))
  281. ax.xaxis.set_minor_locator(mticker.AutoMinorLocator(**kwargs))
  282. ax.yaxis.set_major_locator(mticker.MultipleLocator(1))
  283. ax.yaxis.set_minor_locator(mticker.AutoMinorLocator(**kwargs))
  284. assert_almost_equal(ax.xaxis.get_ticklocs(minor=True), ref)
  285. assert_almost_equal(ax.yaxis.get_ticklocs(minor=True), ref)
  286. class TestLogLocator:
  287. def test_basic(self):
  288. loc = mticker.LogLocator(numticks=5)
  289. with pytest.raises(ValueError):
  290. loc.tick_values(0, 1000)
  291. test_value = np.array([1.00000000e-05, 1.00000000e-03, 1.00000000e-01,
  292. 1.00000000e+01, 1.00000000e+03, 1.00000000e+05,
  293. 1.00000000e+07, 1.000000000e+09])
  294. assert_almost_equal(loc.tick_values(0.001, 1.1e5), test_value)
  295. loc = mticker.LogLocator(base=2)
  296. test_value = np.array([0.5, 1., 2., 4., 8., 16., 32., 64., 128., 256.])
  297. assert_almost_equal(loc.tick_values(1, 100), test_value)
  298. def test_polar_axes(self):
  299. """
  300. Polar Axes have a different ticking logic.
  301. """
  302. fig, ax = plt.subplots(subplot_kw={'projection': 'polar'})
  303. ax.set_yscale('log')
  304. ax.set_ylim(1, 100)
  305. assert_array_equal(ax.get_yticks(), [10, 100, 1000])
  306. def test_switch_to_autolocator(self):
  307. loc = mticker.LogLocator(subs="all")
  308. assert_array_equal(loc.tick_values(0.45, 0.55),
  309. [0.44, 0.46, 0.48, 0.5, 0.52, 0.54, 0.56])
  310. # check that we *skip* 1.0, and 10, because this is a minor locator
  311. loc = mticker.LogLocator(subs=np.arange(2, 10))
  312. assert 1.0 not in loc.tick_values(0.9, 20.)
  313. assert 10.0 not in loc.tick_values(0.9, 20.)
  314. def test_set_params(self):
  315. """
  316. Create log locator with default value, base=10.0, subs=[1.0],
  317. numticks=15 and change it to something else.
  318. See if change was successful. Should not raise exception.
  319. """
  320. loc = mticker.LogLocator()
  321. loc.set_params(numticks=7, subs=[2.0], base=4)
  322. assert loc.numticks == 7
  323. assert loc._base == 4
  324. assert list(loc._subs) == [2.0]
  325. def test_tick_values_correct(self):
  326. ll = mticker.LogLocator(subs=(1, 2, 5))
  327. test_value = np.array([1.e-01, 2.e-01, 5.e-01, 1.e+00, 2.e+00, 5.e+00,
  328. 1.e+01, 2.e+01, 5.e+01, 1.e+02, 2.e+02, 5.e+02,
  329. 1.e+03, 2.e+03, 5.e+03, 1.e+04, 2.e+04, 5.e+04,
  330. 1.e+05, 2.e+05, 5.e+05, 1.e+06, 2.e+06, 5.e+06,
  331. 1.e+07, 2.e+07, 5.e+07, 1.e+08, 2.e+08, 5.e+08])
  332. assert_almost_equal(ll.tick_values(1, 1e7), test_value)
  333. def test_tick_values_not_empty(self):
  334. mpl.rcParams['_internal.classic_mode'] = False
  335. ll = mticker.LogLocator(subs=(1, 2, 5))
  336. test_value = np.array([1.e-01, 2.e-01, 5.e-01, 1.e+00, 2.e+00, 5.e+00,
  337. 1.e+01, 2.e+01, 5.e+01, 1.e+02, 2.e+02, 5.e+02,
  338. 1.e+03, 2.e+03, 5.e+03, 1.e+04, 2.e+04, 5.e+04,
  339. 1.e+05, 2.e+05, 5.e+05, 1.e+06, 2.e+06, 5.e+06,
  340. 1.e+07, 2.e+07, 5.e+07, 1.e+08, 2.e+08, 5.e+08,
  341. 1.e+09, 2.e+09, 5.e+09])
  342. assert_almost_equal(ll.tick_values(1, 1e8), test_value)
  343. def test_multiple_shared_axes(self):
  344. rng = np.random.default_rng(19680801)
  345. dummy_data = [rng.normal(size=100), [], []]
  346. fig, axes = plt.subplots(len(dummy_data), sharex=True, sharey=True)
  347. for ax, data in zip(axes.flatten(), dummy_data):
  348. ax.hist(data, bins=10)
  349. ax.set_yscale('log', nonpositive='clip')
  350. for ax in axes.flatten():
  351. assert all(ax.get_yticks() == axes[0].get_yticks())
  352. assert ax.get_ylim() == axes[0].get_ylim()
  353. class TestNullLocator:
  354. def test_set_params(self):
  355. """
  356. Create null locator, and attempt to call set_params() on it.
  357. Should not exception, and should raise a warning.
  358. """
  359. loc = mticker.NullLocator()
  360. with pytest.warns(UserWarning):
  361. loc.set_params()
  362. class _LogitHelper:
  363. @staticmethod
  364. def isclose(x, y):
  365. return (np.isclose(-np.log(1/x-1), -np.log(1/y-1))
  366. if 0 < x < 1 and 0 < y < 1 else False)
  367. @staticmethod
  368. def assert_almost_equal(x, y):
  369. ax = np.array(x)
  370. ay = np.array(y)
  371. assert np.all(ax > 0) and np.all(ax < 1)
  372. assert np.all(ay > 0) and np.all(ay < 1)
  373. lx = -np.log(1/ax-1)
  374. ly = -np.log(1/ay-1)
  375. assert_almost_equal(lx, ly)
  376. class TestLogitLocator:
  377. ref_basic_limits = [
  378. (5e-2, 1 - 5e-2),
  379. (5e-3, 1 - 5e-3),
  380. (5e-4, 1 - 5e-4),
  381. (5e-5, 1 - 5e-5),
  382. (5e-6, 1 - 5e-6),
  383. (5e-7, 1 - 5e-7),
  384. (5e-8, 1 - 5e-8),
  385. (5e-9, 1 - 5e-9),
  386. ]
  387. ref_basic_major_ticks = [
  388. 1 / (10 ** np.arange(1, 3)),
  389. 1 / (10 ** np.arange(1, 4)),
  390. 1 / (10 ** np.arange(1, 5)),
  391. 1 / (10 ** np.arange(1, 6)),
  392. 1 / (10 ** np.arange(1, 7)),
  393. 1 / (10 ** np.arange(1, 8)),
  394. 1 / (10 ** np.arange(1, 9)),
  395. 1 / (10 ** np.arange(1, 10)),
  396. ]
  397. ref_maxn_limits = [(0.4, 0.6), (5e-2, 2e-1), (1 - 2e-1, 1 - 5e-2)]
  398. @pytest.mark.parametrize(
  399. "lims, expected_low_ticks",
  400. zip(ref_basic_limits, ref_basic_major_ticks),
  401. )
  402. def test_basic_major(self, lims, expected_low_ticks):
  403. """
  404. Create logit locator with huge number of major, and tests ticks.
  405. """
  406. expected_ticks = sorted(
  407. [*expected_low_ticks, 0.5, *(1 - expected_low_ticks)]
  408. )
  409. loc = mticker.LogitLocator(nbins=100)
  410. _LogitHelper.assert_almost_equal(
  411. loc.tick_values(*lims),
  412. expected_ticks
  413. )
  414. @pytest.mark.parametrize("lims", ref_maxn_limits)
  415. def test_maxn_major(self, lims):
  416. """
  417. When the axis is zoomed, the locator must have the same behavior as
  418. MaxNLocator.
  419. """
  420. loc = mticker.LogitLocator(nbins=100)
  421. maxn_loc = mticker.MaxNLocator(nbins=100, steps=[1, 2, 5, 10])
  422. for nbins in (4, 8, 16):
  423. loc.set_params(nbins=nbins)
  424. maxn_loc.set_params(nbins=nbins)
  425. ticks = loc.tick_values(*lims)
  426. maxn_ticks = maxn_loc.tick_values(*lims)
  427. assert ticks.shape == maxn_ticks.shape
  428. assert (ticks == maxn_ticks).all()
  429. @pytest.mark.parametrize("lims", ref_basic_limits + ref_maxn_limits)
  430. def test_nbins_major(self, lims):
  431. """
  432. Assert logit locator for respecting nbins param.
  433. """
  434. basic_needed = int(-np.floor(np.log10(lims[0]))) * 2 + 1
  435. loc = mticker.LogitLocator(nbins=100)
  436. for nbins in range(basic_needed, 2, -1):
  437. loc.set_params(nbins=nbins)
  438. assert len(loc.tick_values(*lims)) <= nbins + 2
  439. @pytest.mark.parametrize(
  440. "lims, expected_low_ticks",
  441. zip(ref_basic_limits, ref_basic_major_ticks),
  442. )
  443. def test_minor(self, lims, expected_low_ticks):
  444. """
  445. In large scale, test the presence of minor,
  446. and assert no minor when major are subsampled.
  447. """
  448. expected_ticks = sorted(
  449. [*expected_low_ticks, 0.5, *(1 - expected_low_ticks)]
  450. )
  451. basic_needed = len(expected_ticks)
  452. loc = mticker.LogitLocator(nbins=100)
  453. minor_loc = mticker.LogitLocator(nbins=100, minor=True)
  454. for nbins in range(basic_needed, 2, -1):
  455. loc.set_params(nbins=nbins)
  456. minor_loc.set_params(nbins=nbins)
  457. major_ticks = loc.tick_values(*lims)
  458. minor_ticks = minor_loc.tick_values(*lims)
  459. if len(major_ticks) >= len(expected_ticks):
  460. # no subsample, we must have a lot of minors ticks
  461. assert (len(major_ticks) - 1) * 5 < len(minor_ticks)
  462. else:
  463. # subsample
  464. _LogitHelper.assert_almost_equal(
  465. sorted([*major_ticks, *minor_ticks]), expected_ticks)
  466. def test_minor_attr(self):
  467. loc = mticker.LogitLocator(nbins=100)
  468. assert not loc.minor
  469. loc.minor = True
  470. assert loc.minor
  471. loc.set_params(minor=False)
  472. assert not loc.minor
  473. acceptable_vmin_vmax = [
  474. *(2.5 ** np.arange(-3, 0)),
  475. *(1 - 2.5 ** np.arange(-3, 0)),
  476. ]
  477. @pytest.mark.parametrize(
  478. "lims",
  479. [
  480. (a, b)
  481. for (a, b) in itertools.product(acceptable_vmin_vmax, repeat=2)
  482. if a != b
  483. ],
  484. )
  485. def test_nonsingular_ok(self, lims):
  486. """
  487. Create logit locator, and test the nonsingular method for acceptable
  488. value
  489. """
  490. loc = mticker.LogitLocator()
  491. lims2 = loc.nonsingular(*lims)
  492. assert sorted(lims) == sorted(lims2)
  493. @pytest.mark.parametrize("okval", acceptable_vmin_vmax)
  494. def test_nonsingular_nok(self, okval):
  495. """
  496. Create logit locator, and test the nonsingular method for non
  497. acceptable value
  498. """
  499. loc = mticker.LogitLocator()
  500. vmin, vmax = (-1, okval)
  501. vmin2, vmax2 = loc.nonsingular(vmin, vmax)
  502. assert vmax2 == vmax
  503. assert 0 < vmin2 < vmax2
  504. vmin, vmax = (okval, 2)
  505. vmin2, vmax2 = loc.nonsingular(vmin, vmax)
  506. assert vmin2 == vmin
  507. assert vmin2 < vmax2 < 1
  508. class TestFixedLocator:
  509. def test_set_params(self):
  510. """
  511. Create fixed locator with 5 nbins, and change it to something else.
  512. See if change was successful.
  513. Should not exception.
  514. """
  515. fixed = mticker.FixedLocator(range(0, 24), nbins=5)
  516. fixed.set_params(nbins=7)
  517. assert fixed.nbins == 7
  518. class TestIndexLocator:
  519. def test_set_params(self):
  520. """
  521. Create index locator with 3 base, 4 offset. and change it to something
  522. else. See if change was successful.
  523. Should not exception.
  524. """
  525. index = mticker.IndexLocator(base=3, offset=4)
  526. index.set_params(base=7, offset=7)
  527. assert index._base == 7
  528. assert index.offset == 7
  529. class TestSymmetricalLogLocator:
  530. def test_set_params(self):
  531. """
  532. Create symmetrical log locator with default subs =[1.0] numticks = 15,
  533. and change it to something else.
  534. See if change was successful.
  535. Should not exception.
  536. """
  537. sym = mticker.SymmetricalLogLocator(base=10, linthresh=1)
  538. sym.set_params(subs=[2.0], numticks=8)
  539. assert sym._subs == [2.0]
  540. assert sym.numticks == 8
  541. @pytest.mark.parametrize(
  542. 'vmin, vmax, expected',
  543. [
  544. (0, 1, [0, 1]),
  545. (-1, 1, [-1, 0, 1]),
  546. ],
  547. )
  548. def test_values(self, vmin, vmax, expected):
  549. # https://github.com/matplotlib/matplotlib/issues/25945
  550. sym = mticker.SymmetricalLogLocator(base=10, linthresh=1)
  551. ticks = sym.tick_values(vmin=vmin, vmax=vmax)
  552. assert_array_equal(ticks, expected)
  553. def test_subs(self):
  554. sym = mticker.SymmetricalLogLocator(base=10, linthresh=1, subs=[2.0, 4.0])
  555. sym.create_dummy_axis()
  556. sym.axis.set_view_interval(-10, 10)
  557. assert_array_equal(sym(), [-20, -40, -2, -4, 0, 2, 4, 20, 40])
  558. def test_extending(self):
  559. sym = mticker.SymmetricalLogLocator(base=10, linthresh=1)
  560. sym.create_dummy_axis()
  561. sym.axis.set_view_interval(8, 9)
  562. assert (sym() == [1.0]).all()
  563. sym.axis.set_view_interval(8, 12)
  564. assert (sym() == [1.0, 10.0]).all()
  565. assert sym.view_limits(10, 10) == (1, 100)
  566. assert sym.view_limits(-10, -10) == (-100, -1)
  567. assert sym.view_limits(0, 0) == (-0.001, 0.001)
  568. class TestAsinhLocator:
  569. def test_init(self):
  570. lctr = mticker.AsinhLocator(linear_width=2.718, numticks=19)
  571. assert lctr.linear_width == 2.718
  572. assert lctr.numticks == 19
  573. assert lctr.base == 10
  574. def test_set_params(self):
  575. lctr = mticker.AsinhLocator(linear_width=5,
  576. numticks=17, symthresh=0.125,
  577. base=4, subs=(2.5, 3.25))
  578. assert lctr.numticks == 17
  579. assert lctr.symthresh == 0.125
  580. assert lctr.base == 4
  581. assert lctr.subs == (2.5, 3.25)
  582. lctr.set_params(numticks=23)
  583. assert lctr.numticks == 23
  584. lctr.set_params(None)
  585. assert lctr.numticks == 23
  586. lctr.set_params(symthresh=0.5)
  587. assert lctr.symthresh == 0.5
  588. lctr.set_params(symthresh=None)
  589. assert lctr.symthresh == 0.5
  590. lctr.set_params(base=7)
  591. assert lctr.base == 7
  592. lctr.set_params(base=None)
  593. assert lctr.base == 7
  594. lctr.set_params(subs=(2, 4.125))
  595. assert lctr.subs == (2, 4.125)
  596. lctr.set_params(subs=None)
  597. assert lctr.subs == (2, 4.125)
  598. lctr.set_params(subs=[])
  599. assert lctr.subs is None
  600. def test_linear_values(self):
  601. lctr = mticker.AsinhLocator(linear_width=100, numticks=11, base=0)
  602. assert_almost_equal(lctr.tick_values(-1, 1),
  603. np.arange(-1, 1.01, 0.2))
  604. assert_almost_equal(lctr.tick_values(-0.1, 0.1),
  605. np.arange(-0.1, 0.101, 0.02))
  606. assert_almost_equal(lctr.tick_values(-0.01, 0.01),
  607. np.arange(-0.01, 0.0101, 0.002))
  608. def test_wide_values(self):
  609. lctr = mticker.AsinhLocator(linear_width=0.1, numticks=11, base=0)
  610. assert_almost_equal(lctr.tick_values(-100, 100),
  611. [-100, -20, -5, -1, -0.2,
  612. 0, 0.2, 1, 5, 20, 100])
  613. assert_almost_equal(lctr.tick_values(-1000, 1000),
  614. [-1000, -100, -20, -3, -0.4,
  615. 0, 0.4, 3, 20, 100, 1000])
  616. def test_near_zero(self):
  617. """Check that manually injected zero will supersede nearby tick"""
  618. lctr = mticker.AsinhLocator(linear_width=100, numticks=3, base=0)
  619. assert_almost_equal(lctr.tick_values(-1.1, 0.9), [-1.0, 0.0, 0.9])
  620. def test_fallback(self):
  621. lctr = mticker.AsinhLocator(1.0, numticks=11)
  622. assert_almost_equal(lctr.tick_values(101, 102),
  623. np.arange(101, 102.01, 0.1))
  624. def test_symmetrizing(self):
  625. lctr = mticker.AsinhLocator(linear_width=1, numticks=3,
  626. symthresh=0.25, base=0)
  627. lctr.create_dummy_axis()
  628. lctr.axis.set_view_interval(-1, 2)
  629. assert_almost_equal(lctr(), [-1, 0, 2])
  630. lctr.axis.set_view_interval(-1, 0.9)
  631. assert_almost_equal(lctr(), [-1, 0, 1])
  632. lctr.axis.set_view_interval(-0.85, 1.05)
  633. assert_almost_equal(lctr(), [-1, 0, 1])
  634. lctr.axis.set_view_interval(1, 1.1)
  635. assert_almost_equal(lctr(), [1, 1.05, 1.1])
  636. def test_base_rounding(self):
  637. lctr10 = mticker.AsinhLocator(linear_width=1, numticks=8,
  638. base=10, subs=(1, 3, 5))
  639. assert_almost_equal(lctr10.tick_values(-110, 110),
  640. [-500, -300, -100, -50, -30, -10, -5, -3, -1,
  641. -0.5, -0.3, -0.1, 0, 0.1, 0.3, 0.5,
  642. 1, 3, 5, 10, 30, 50, 100, 300, 500])
  643. lctr5 = mticker.AsinhLocator(linear_width=1, numticks=20, base=5)
  644. assert_almost_equal(lctr5.tick_values(-1050, 1050),
  645. [-625, -125, -25, -5, -1, -0.2, 0,
  646. 0.2, 1, 5, 25, 125, 625])
  647. class TestScalarFormatter:
  648. offset_data = [
  649. (123, 189, 0),
  650. (-189, -123, 0),
  651. (12341, 12349, 12340),
  652. (-12349, -12341, -12340),
  653. (99999.5, 100010.5, 100000),
  654. (-100010.5, -99999.5, -100000),
  655. (99990.5, 100000.5, 100000),
  656. (-100000.5, -99990.5, -100000),
  657. (1233999, 1234001, 1234000),
  658. (-1234001, -1233999, -1234000),
  659. (1, 1, 1),
  660. (123, 123, 0),
  661. # Test cases courtesy of @WeatherGod
  662. (.4538, .4578, .45),
  663. (3789.12, 3783.1, 3780),
  664. (45124.3, 45831.75, 45000),
  665. (0.000721, 0.0007243, 0.00072),
  666. (12592.82, 12591.43, 12590),
  667. (9., 12., 0),
  668. (900., 1200., 0),
  669. (1900., 1200., 0),
  670. (0.99, 1.01, 1),
  671. (9.99, 10.01, 10),
  672. (99.99, 100.01, 100),
  673. (5.99, 6.01, 6),
  674. (15.99, 16.01, 16),
  675. (-0.452, 0.492, 0),
  676. (-0.492, 0.492, 0),
  677. (12331.4, 12350.5, 12300),
  678. (-12335.3, 12335.3, 0),
  679. ]
  680. use_offset_data = [True, False]
  681. useMathText_data = [True, False]
  682. # (sci_type, scilimits, lim, orderOfMag, fewticks)
  683. scilimits_data = [
  684. (False, (0, 0), (10.0, 20.0), 0, False),
  685. (True, (-2, 2), (-10, 20), 0, False),
  686. (True, (-2, 2), (-20, 10), 0, False),
  687. (True, (-2, 2), (-110, 120), 2, False),
  688. (True, (-2, 2), (-120, 110), 2, False),
  689. (True, (-2, 2), (-.001, 0.002), -3, False),
  690. (True, (-7, 7), (0.18e10, 0.83e10), 9, True),
  691. (True, (0, 0), (-1e5, 1e5), 5, False),
  692. (True, (6, 6), (-1e5, 1e5), 6, False),
  693. ]
  694. cursor_data = [
  695. [0., "0.000"],
  696. [0.0123, "0.012"],
  697. [0.123, "0.123"],
  698. [1.23, "1.230"],
  699. [12.3, "12.300"],
  700. ]
  701. format_data = [
  702. (.1, "1e-1"),
  703. (.11, "1.1e-1"),
  704. (1e8, "1e8"),
  705. (1.1e8, "1.1e8"),
  706. ]
  707. @pytest.mark.parametrize('unicode_minus, result',
  708. [(True, "\N{MINUS SIGN}1"), (False, "-1")])
  709. def test_unicode_minus(self, unicode_minus, result):
  710. mpl.rcParams['axes.unicode_minus'] = unicode_minus
  711. assert (
  712. plt.gca().xaxis.get_major_formatter().format_data_short(-1).strip()
  713. == result)
  714. @pytest.mark.parametrize('left, right, offset', offset_data)
  715. def test_offset_value(self, left, right, offset):
  716. fig, ax = plt.subplots()
  717. formatter = ax.xaxis.get_major_formatter()
  718. with (pytest.warns(UserWarning, match='Attempting to set identical')
  719. if left == right else nullcontext()):
  720. ax.set_xlim(left, right)
  721. ax.xaxis._update_ticks()
  722. assert formatter.offset == offset
  723. with (pytest.warns(UserWarning, match='Attempting to set identical')
  724. if left == right else nullcontext()):
  725. ax.set_xlim(right, left)
  726. ax.xaxis._update_ticks()
  727. assert formatter.offset == offset
  728. @pytest.mark.parametrize('use_offset', use_offset_data)
  729. def test_use_offset(self, use_offset):
  730. with mpl.rc_context({'axes.formatter.useoffset': use_offset}):
  731. tmp_form = mticker.ScalarFormatter()
  732. assert use_offset == tmp_form.get_useOffset()
  733. assert tmp_form.offset == 0
  734. @pytest.mark.parametrize('use_math_text', useMathText_data)
  735. def test_useMathText(self, use_math_text):
  736. with mpl.rc_context({'axes.formatter.use_mathtext': use_math_text}):
  737. tmp_form = mticker.ScalarFormatter()
  738. assert use_math_text == tmp_form.get_useMathText()
  739. def test_set_use_offset_float(self):
  740. tmp_form = mticker.ScalarFormatter()
  741. tmp_form.set_useOffset(0.5)
  742. assert not tmp_form.get_useOffset()
  743. assert tmp_form.offset == 0.5
  744. def test_use_locale(self):
  745. conv = locale.localeconv()
  746. sep = conv['thousands_sep']
  747. if not sep or conv['grouping'][-1:] in ([], [locale.CHAR_MAX]):
  748. pytest.skip('Locale does not apply grouping') # pragma: no cover
  749. with mpl.rc_context({'axes.formatter.use_locale': True}):
  750. tmp_form = mticker.ScalarFormatter()
  751. assert tmp_form.get_useLocale()
  752. tmp_form.create_dummy_axis()
  753. tmp_form.axis.set_data_interval(0, 10)
  754. tmp_form.set_locs([1, 2, 3])
  755. assert sep in tmp_form(1e9)
  756. @pytest.mark.parametrize(
  757. 'sci_type, scilimits, lim, orderOfMag, fewticks', scilimits_data)
  758. def test_scilimits(self, sci_type, scilimits, lim, orderOfMag, fewticks):
  759. tmp_form = mticker.ScalarFormatter()
  760. tmp_form.set_scientific(sci_type)
  761. tmp_form.set_powerlimits(scilimits)
  762. fig, ax = plt.subplots()
  763. ax.yaxis.set_major_formatter(tmp_form)
  764. ax.set_ylim(*lim)
  765. if fewticks:
  766. ax.yaxis.set_major_locator(mticker.MaxNLocator(4))
  767. tmp_form.set_locs(ax.yaxis.get_majorticklocs())
  768. assert orderOfMag == tmp_form.orderOfMagnitude
  769. @pytest.mark.parametrize('value, expected', format_data)
  770. def test_format_data(self, value, expected):
  771. mpl.rcParams['axes.unicode_minus'] = False
  772. sf = mticker.ScalarFormatter()
  773. assert sf.format_data(value) == expected
  774. @pytest.mark.parametrize('data, expected', cursor_data)
  775. def test_cursor_precision(self, data, expected):
  776. fig, ax = plt.subplots()
  777. ax.set_xlim(-1, 1) # Pointing precision of 0.001.
  778. fmt = ax.xaxis.get_major_formatter().format_data_short
  779. assert fmt(data) == expected
  780. @pytest.mark.parametrize('data, expected', cursor_data)
  781. def test_cursor_dummy_axis(self, data, expected):
  782. # Issue #17624
  783. sf = mticker.ScalarFormatter()
  784. sf.create_dummy_axis()
  785. sf.axis.set_view_interval(0, 10)
  786. fmt = sf.format_data_short
  787. assert fmt(data) == expected
  788. assert sf.axis.get_tick_space() == 9
  789. assert sf.axis.get_minpos() == 0
  790. def test_mathtext_ticks(self):
  791. mpl.rcParams.update({
  792. 'font.family': 'serif',
  793. 'font.serif': 'cmr10',
  794. 'axes.formatter.use_mathtext': False
  795. })
  796. if parse_version(pytest.__version__).major < 8:
  797. with pytest.warns(UserWarning, match='cmr10 font should ideally'):
  798. fig, ax = plt.subplots()
  799. ax.set_xticks([-1, 0, 1])
  800. fig.canvas.draw()
  801. else:
  802. with (pytest.warns(UserWarning, match="Glyph 8722"),
  803. pytest.warns(UserWarning, match='cmr10 font should ideally')):
  804. fig, ax = plt.subplots()
  805. ax.set_xticks([-1, 0, 1])
  806. fig.canvas.draw()
  807. def test_cmr10_substitutions(self, caplog):
  808. mpl.rcParams.update({
  809. 'font.family': 'cmr10',
  810. 'mathtext.fontset': 'cm',
  811. 'axes.formatter.use_mathtext': True,
  812. })
  813. # Test that it does not log a warning about missing glyphs.
  814. with caplog.at_level(logging.WARNING, logger='matplotlib.mathtext'):
  815. fig, ax = plt.subplots()
  816. ax.plot([-0.03, 0.05], [40, 0.05])
  817. ax.set_yscale('log')
  818. yticks = [0.02, 0.3, 4, 50]
  819. formatter = mticker.LogFormatterSciNotation()
  820. ax.set_yticks(yticks, map(formatter, yticks))
  821. fig.canvas.draw()
  822. assert not caplog.text
  823. def test_empty_locs(self):
  824. sf = mticker.ScalarFormatter()
  825. sf.set_locs([])
  826. assert sf(0.5) == ''
  827. class TestLogFormatterExponent:
  828. param_data = [
  829. (True, 4, np.arange(-3, 4.0), np.arange(-3, 4.0),
  830. ['-3', '-2', '-1', '0', '1', '2', '3']),
  831. # With labelOnlyBase=False, non-integer powers should be nicely
  832. # formatted.
  833. (False, 10, np.array([0.1, 0.00001, np.pi, 0.2, -0.2, -0.00001]),
  834. range(6), ['0.1', '1e-05', '3.14', '0.2', '-0.2', '-1e-05']),
  835. (False, 50, np.array([3, 5, 12, 42], dtype=float), range(6),
  836. ['3', '5', '12', '42']),
  837. ]
  838. base_data = [2.0, 5.0, 10.0, np.pi, np.e]
  839. @pytest.mark.parametrize(
  840. 'labelOnlyBase, exponent, locs, positions, expected', param_data)
  841. @pytest.mark.parametrize('base', base_data)
  842. def test_basic(self, labelOnlyBase, base, exponent, locs, positions,
  843. expected):
  844. formatter = mticker.LogFormatterExponent(base=base,
  845. labelOnlyBase=labelOnlyBase)
  846. formatter.create_dummy_axis()
  847. formatter.axis.set_view_interval(1, base**exponent)
  848. vals = base**locs
  849. labels = [formatter(x, pos) for (x, pos) in zip(vals, positions)]
  850. expected = [label.replace('-', '\N{Minus Sign}') for label in expected]
  851. assert labels == expected
  852. def test_blank(self):
  853. # Should be a blank string for non-integer powers if labelOnlyBase=True
  854. formatter = mticker.LogFormatterExponent(base=10, labelOnlyBase=True)
  855. formatter.create_dummy_axis()
  856. formatter.axis.set_view_interval(1, 10)
  857. assert formatter(10**0.1) == ''
  858. class TestLogFormatterMathtext:
  859. fmt = mticker.LogFormatterMathtext()
  860. test_data = [
  861. (0, 1, '$\\mathdefault{10^{0}}$'),
  862. (0, 1e-2, '$\\mathdefault{10^{-2}}$'),
  863. (0, 1e2, '$\\mathdefault{10^{2}}$'),
  864. (3, 1, '$\\mathdefault{1}$'),
  865. (3, 1e-2, '$\\mathdefault{0.01}$'),
  866. (3, 1e2, '$\\mathdefault{100}$'),
  867. (3, 1e-3, '$\\mathdefault{10^{-3}}$'),
  868. (3, 1e3, '$\\mathdefault{10^{3}}$'),
  869. ]
  870. @pytest.mark.parametrize('min_exponent, value, expected', test_data)
  871. def test_min_exponent(self, min_exponent, value, expected):
  872. with mpl.rc_context({'axes.formatter.min_exponent': min_exponent}):
  873. assert self.fmt(value) == expected
  874. class TestLogFormatterSciNotation:
  875. test_data = [
  876. (2, 0.03125, '$\\mathdefault{2^{-5}}$'),
  877. (2, 1, '$\\mathdefault{2^{0}}$'),
  878. (2, 32, '$\\mathdefault{2^{5}}$'),
  879. (2, 0.0375, '$\\mathdefault{1.2\\times2^{-5}}$'),
  880. (2, 1.2, '$\\mathdefault{1.2\\times2^{0}}$'),
  881. (2, 38.4, '$\\mathdefault{1.2\\times2^{5}}$'),
  882. (10, -1, '$\\mathdefault{-10^{0}}$'),
  883. (10, 1e-05, '$\\mathdefault{10^{-5}}$'),
  884. (10, 1, '$\\mathdefault{10^{0}}$'),
  885. (10, 100000, '$\\mathdefault{10^{5}}$'),
  886. (10, 2e-05, '$\\mathdefault{2\\times10^{-5}}$'),
  887. (10, 2, '$\\mathdefault{2\\times10^{0}}$'),
  888. (10, 200000, '$\\mathdefault{2\\times10^{5}}$'),
  889. (10, 5e-05, '$\\mathdefault{5\\times10^{-5}}$'),
  890. (10, 5, '$\\mathdefault{5\\times10^{0}}$'),
  891. (10, 500000, '$\\mathdefault{5\\times10^{5}}$'),
  892. ]
  893. @mpl.style.context('default')
  894. @pytest.mark.parametrize('base, value, expected', test_data)
  895. def test_basic(self, base, value, expected):
  896. formatter = mticker.LogFormatterSciNotation(base=base)
  897. with mpl.rc_context({'text.usetex': False}):
  898. assert formatter(value) == expected
  899. class TestLogFormatter:
  900. pprint_data = [
  901. (3.141592654e-05, 0.001, '3.142e-5'),
  902. (0.0003141592654, 0.001, '3.142e-4'),
  903. (0.003141592654, 0.001, '3.142e-3'),
  904. (0.03141592654, 0.001, '3.142e-2'),
  905. (0.3141592654, 0.001, '3.142e-1'),
  906. (3.141592654, 0.001, '3.142'),
  907. (31.41592654, 0.001, '3.142e1'),
  908. (314.1592654, 0.001, '3.142e2'),
  909. (3141.592654, 0.001, '3.142e3'),
  910. (31415.92654, 0.001, '3.142e4'),
  911. (314159.2654, 0.001, '3.142e5'),
  912. (1e-05, 0.001, '1e-5'),
  913. (0.0001, 0.001, '1e-4'),
  914. (0.001, 0.001, '1e-3'),
  915. (0.01, 0.001, '1e-2'),
  916. (0.1, 0.001, '1e-1'),
  917. (1, 0.001, '1'),
  918. (10, 0.001, '10'),
  919. (100, 0.001, '100'),
  920. (1000, 0.001, '1000'),
  921. (10000, 0.001, '1e4'),
  922. (100000, 0.001, '1e5'),
  923. (3.141592654e-05, 0.015, '0'),
  924. (0.0003141592654, 0.015, '0'),
  925. (0.003141592654, 0.015, '0.003'),
  926. (0.03141592654, 0.015, '0.031'),
  927. (0.3141592654, 0.015, '0.314'),
  928. (3.141592654, 0.015, '3.142'),
  929. (31.41592654, 0.015, '31.416'),
  930. (314.1592654, 0.015, '314.159'),
  931. (3141.592654, 0.015, '3141.593'),
  932. (31415.92654, 0.015, '31415.927'),
  933. (314159.2654, 0.015, '314159.265'),
  934. (1e-05, 0.015, '0'),
  935. (0.0001, 0.015, '0'),
  936. (0.001, 0.015, '0.001'),
  937. (0.01, 0.015, '0.01'),
  938. (0.1, 0.015, '0.1'),
  939. (1, 0.015, '1'),
  940. (10, 0.015, '10'),
  941. (100, 0.015, '100'),
  942. (1000, 0.015, '1000'),
  943. (10000, 0.015, '10000'),
  944. (100000, 0.015, '100000'),
  945. (3.141592654e-05, 0.5, '0'),
  946. (0.0003141592654, 0.5, '0'),
  947. (0.003141592654, 0.5, '0.003'),
  948. (0.03141592654, 0.5, '0.031'),
  949. (0.3141592654, 0.5, '0.314'),
  950. (3.141592654, 0.5, '3.142'),
  951. (31.41592654, 0.5, '31.416'),
  952. (314.1592654, 0.5, '314.159'),
  953. (3141.592654, 0.5, '3141.593'),
  954. (31415.92654, 0.5, '31415.927'),
  955. (314159.2654, 0.5, '314159.265'),
  956. (1e-05, 0.5, '0'),
  957. (0.0001, 0.5, '0'),
  958. (0.001, 0.5, '0.001'),
  959. (0.01, 0.5, '0.01'),
  960. (0.1, 0.5, '0.1'),
  961. (1, 0.5, '1'),
  962. (10, 0.5, '10'),
  963. (100, 0.5, '100'),
  964. (1000, 0.5, '1000'),
  965. (10000, 0.5, '10000'),
  966. (100000, 0.5, '100000'),
  967. (3.141592654e-05, 5, '0'),
  968. (0.0003141592654, 5, '0'),
  969. (0.003141592654, 5, '0'),
  970. (0.03141592654, 5, '0.03'),
  971. (0.3141592654, 5, '0.31'),
  972. (3.141592654, 5, '3.14'),
  973. (31.41592654, 5, '31.42'),
  974. (314.1592654, 5, '314.16'),
  975. (3141.592654, 5, '3141.59'),
  976. (31415.92654, 5, '31415.93'),
  977. (314159.2654, 5, '314159.27'),
  978. (1e-05, 5, '0'),
  979. (0.0001, 5, '0'),
  980. (0.001, 5, '0'),
  981. (0.01, 5, '0.01'),
  982. (0.1, 5, '0.1'),
  983. (1, 5, '1'),
  984. (10, 5, '10'),
  985. (100, 5, '100'),
  986. (1000, 5, '1000'),
  987. (10000, 5, '10000'),
  988. (100000, 5, '100000'),
  989. (3.141592654e-05, 100, '0'),
  990. (0.0003141592654, 100, '0'),
  991. (0.003141592654, 100, '0'),
  992. (0.03141592654, 100, '0'),
  993. (0.3141592654, 100, '0.3'),
  994. (3.141592654, 100, '3.1'),
  995. (31.41592654, 100, '31.4'),
  996. (314.1592654, 100, '314.2'),
  997. (3141.592654, 100, '3141.6'),
  998. (31415.92654, 100, '31415.9'),
  999. (314159.2654, 100, '314159.3'),
  1000. (1e-05, 100, '0'),
  1001. (0.0001, 100, '0'),
  1002. (0.001, 100, '0'),
  1003. (0.01, 100, '0'),
  1004. (0.1, 100, '0.1'),
  1005. (1, 100, '1'),
  1006. (10, 100, '10'),
  1007. (100, 100, '100'),
  1008. (1000, 100, '1000'),
  1009. (10000, 100, '10000'),
  1010. (100000, 100, '100000'),
  1011. (3.141592654e-05, 1000000.0, '3.1e-5'),
  1012. (0.0003141592654, 1000000.0, '3.1e-4'),
  1013. (0.003141592654, 1000000.0, '3.1e-3'),
  1014. (0.03141592654, 1000000.0, '3.1e-2'),
  1015. (0.3141592654, 1000000.0, '3.1e-1'),
  1016. (3.141592654, 1000000.0, '3.1'),
  1017. (31.41592654, 1000000.0, '3.1e1'),
  1018. (314.1592654, 1000000.0, '3.1e2'),
  1019. (3141.592654, 1000000.0, '3.1e3'),
  1020. (31415.92654, 1000000.0, '3.1e4'),
  1021. (314159.2654, 1000000.0, '3.1e5'),
  1022. (1e-05, 1000000.0, '1e-5'),
  1023. (0.0001, 1000000.0, '1e-4'),
  1024. (0.001, 1000000.0, '1e-3'),
  1025. (0.01, 1000000.0, '1e-2'),
  1026. (0.1, 1000000.0, '1e-1'),
  1027. (1, 1000000.0, '1'),
  1028. (10, 1000000.0, '10'),
  1029. (100, 1000000.0, '100'),
  1030. (1000, 1000000.0, '1000'),
  1031. (10000, 1000000.0, '1e4'),
  1032. (100000, 1000000.0, '1e5'),
  1033. ]
  1034. @pytest.mark.parametrize('value, domain, expected', pprint_data)
  1035. def test_pprint(self, value, domain, expected):
  1036. fmt = mticker.LogFormatter()
  1037. label = fmt._pprint_val(value, domain)
  1038. assert label == expected
  1039. @pytest.mark.parametrize('value, long, short', [
  1040. (0.0, "0", "0"),
  1041. (0, "0", "0"),
  1042. (-1.0, "-10^0", "-1"),
  1043. (2e-10, "2x10^-10", "2e-10"),
  1044. (1e10, "10^10", "1e+10"),
  1045. ])
  1046. def test_format_data(self, value, long, short):
  1047. fig, ax = plt.subplots()
  1048. ax.set_xscale('log')
  1049. fmt = ax.xaxis.get_major_formatter()
  1050. assert fmt.format_data(value) == long
  1051. assert fmt.format_data_short(value) == short
  1052. def _sub_labels(self, axis, subs=()):
  1053. """Test whether locator marks subs to be labeled."""
  1054. fmt = axis.get_minor_formatter()
  1055. minor_tlocs = axis.get_minorticklocs()
  1056. fmt.set_locs(minor_tlocs)
  1057. coefs = minor_tlocs / 10**(np.floor(np.log10(minor_tlocs)))
  1058. label_expected = [round(c) in subs for c in coefs]
  1059. label_test = [fmt(x) != '' for x in minor_tlocs]
  1060. assert label_test == label_expected
  1061. @mpl.style.context('default')
  1062. def test_sublabel(self):
  1063. # test label locator
  1064. fig, ax = plt.subplots()
  1065. ax.set_xscale('log')
  1066. ax.xaxis.set_major_locator(mticker.LogLocator(base=10, subs=[]))
  1067. ax.xaxis.set_minor_locator(mticker.LogLocator(base=10,
  1068. subs=np.arange(2, 10)))
  1069. ax.xaxis.set_major_formatter(mticker.LogFormatter(labelOnlyBase=True))
  1070. ax.xaxis.set_minor_formatter(mticker.LogFormatter(labelOnlyBase=False))
  1071. # axis range above 3 decades, only bases are labeled
  1072. ax.set_xlim(1, 1e4)
  1073. fmt = ax.xaxis.get_major_formatter()
  1074. fmt.set_locs(ax.xaxis.get_majorticklocs())
  1075. show_major_labels = [fmt(x) != ''
  1076. for x in ax.xaxis.get_majorticklocs()]
  1077. assert np.all(show_major_labels)
  1078. self._sub_labels(ax.xaxis, subs=[])
  1079. # For the next two, if the numdec threshold in LogFormatter.set_locs
  1080. # were 3, then the label sub would be 3 for 2-3 decades and (2, 5)
  1081. # for 1-2 decades. With a threshold of 1, subs are not labeled.
  1082. # axis range at 2 to 3 decades
  1083. ax.set_xlim(1, 800)
  1084. self._sub_labels(ax.xaxis, subs=[])
  1085. # axis range at 1 to 2 decades
  1086. ax.set_xlim(1, 80)
  1087. self._sub_labels(ax.xaxis, subs=[])
  1088. # axis range at 0.4 to 1 decades, label subs 2, 3, 4, 6
  1089. ax.set_xlim(1, 8)
  1090. self._sub_labels(ax.xaxis, subs=[2, 3, 4, 6])
  1091. # axis range at 0 to 0.4 decades, label all
  1092. ax.set_xlim(0.5, 0.9)
  1093. self._sub_labels(ax.xaxis, subs=np.arange(2, 10, dtype=int))
  1094. @pytest.mark.parametrize('val', [1, 10, 100, 1000])
  1095. def test_LogFormatter_call(self, val):
  1096. # test _num_to_string method used in __call__
  1097. temp_lf = mticker.LogFormatter()
  1098. temp_lf.create_dummy_axis()
  1099. temp_lf.axis.set_view_interval(1, 10)
  1100. assert temp_lf(val) == str(val)
  1101. @pytest.mark.parametrize('val', [1e-323, 2e-323, 10e-323, 11e-323])
  1102. def test_LogFormatter_call_tiny(self, val):
  1103. # test coeff computation in __call__
  1104. temp_lf = mticker.LogFormatter()
  1105. temp_lf.create_dummy_axis()
  1106. temp_lf.axis.set_view_interval(1, 10)
  1107. temp_lf(val)
  1108. class TestLogitFormatter:
  1109. @staticmethod
  1110. def logit_deformatter(string):
  1111. r"""
  1112. Parser to convert string as r'$\mathdefault{1.41\cdot10^{-4}}$' in
  1113. float 1.41e-4, as '0.5' or as r'$\mathdefault{\frac{1}{2}}$' in float
  1114. 0.5,
  1115. """
  1116. match = re.match(
  1117. r"[^\d]*"
  1118. r"(?P<comp>1-)?"
  1119. r"(?P<mant>\d*\.?\d*)?"
  1120. r"(?:\\cdot)?"
  1121. r"(?:10\^\{(?P<expo>-?\d*)})?"
  1122. r"[^\d]*$",
  1123. string,
  1124. )
  1125. if match:
  1126. comp = match["comp"] is not None
  1127. mantissa = float(match["mant"]) if match["mant"] else 1
  1128. expo = int(match["expo"]) if match["expo"] is not None else 0
  1129. value = mantissa * 10 ** expo
  1130. if match["mant"] or match["expo"] is not None:
  1131. if comp:
  1132. return 1 - value
  1133. return value
  1134. match = re.match(
  1135. r"[^\d]*\\frac\{(?P<num>\d+)\}\{(?P<deno>\d+)\}[^\d]*$", string
  1136. )
  1137. if match:
  1138. num, deno = float(match["num"]), float(match["deno"])
  1139. return num / deno
  1140. raise ValueError("Not formatted by LogitFormatter")
  1141. @pytest.mark.parametrize(
  1142. "fx, x",
  1143. [
  1144. (r"STUFF0.41OTHERSTUFF", 0.41),
  1145. (r"STUFF1.41\cdot10^{-2}OTHERSTUFF", 1.41e-2),
  1146. (r"STUFF1-0.41OTHERSTUFF", 1 - 0.41),
  1147. (r"STUFF1-1.41\cdot10^{-2}OTHERSTUFF", 1 - 1.41e-2),
  1148. (r"STUFF", None),
  1149. (r"STUFF12.4e-3OTHERSTUFF", None),
  1150. ],
  1151. )
  1152. def test_logit_deformater(self, fx, x):
  1153. if x is None:
  1154. with pytest.raises(ValueError):
  1155. TestLogitFormatter.logit_deformatter(fx)
  1156. else:
  1157. y = TestLogitFormatter.logit_deformatter(fx)
  1158. assert _LogitHelper.isclose(x, y)
  1159. decade_test = sorted(
  1160. [10 ** (-i) for i in range(1, 10)]
  1161. + [1 - 10 ** (-i) for i in range(1, 10)]
  1162. + [1 / 2]
  1163. )
  1164. @pytest.mark.parametrize("x", decade_test)
  1165. def test_basic(self, x):
  1166. """
  1167. Test the formatted value correspond to the value for ideal ticks in
  1168. logit space.
  1169. """
  1170. formatter = mticker.LogitFormatter(use_overline=False)
  1171. formatter.set_locs(self.decade_test)
  1172. s = formatter(x)
  1173. x2 = TestLogitFormatter.logit_deformatter(s)
  1174. assert _LogitHelper.isclose(x, x2)
  1175. @pytest.mark.parametrize("x", (-1, -0.5, -0.1, 1.1, 1.5, 2))
  1176. def test_invalid(self, x):
  1177. """
  1178. Test that invalid value are formatted with empty string without
  1179. raising exception.
  1180. """
  1181. formatter = mticker.LogitFormatter(use_overline=False)
  1182. formatter.set_locs(self.decade_test)
  1183. s = formatter(x)
  1184. assert s == ""
  1185. @pytest.mark.parametrize("x", 1 / (1 + np.exp(-np.linspace(-7, 7, 10))))
  1186. def test_variablelength(self, x):
  1187. """
  1188. The format length should change depending on the neighbor labels.
  1189. """
  1190. formatter = mticker.LogitFormatter(use_overline=False)
  1191. for N in (10, 20, 50, 100, 200, 1000, 2000, 5000, 10000):
  1192. if x + 1 / N < 1:
  1193. formatter.set_locs([x - 1 / N, x, x + 1 / N])
  1194. sx = formatter(x)
  1195. sx1 = formatter(x + 1 / N)
  1196. d = (
  1197. TestLogitFormatter.logit_deformatter(sx1)
  1198. - TestLogitFormatter.logit_deformatter(sx)
  1199. )
  1200. assert 0 < d < 2 / N
  1201. lims_minor_major = [
  1202. (True, (5e-8, 1 - 5e-8), ((25, False), (75, False))),
  1203. (True, (5e-5, 1 - 5e-5), ((25, False), (75, True))),
  1204. (True, (5e-2, 1 - 5e-2), ((25, True), (75, True))),
  1205. (False, (0.75, 0.76, 0.77), ((7, True), (25, True), (75, True))),
  1206. ]
  1207. @pytest.mark.parametrize("method, lims, cases", lims_minor_major)
  1208. def test_minor_vs_major(self, method, lims, cases):
  1209. """
  1210. Test minor/major displays.
  1211. """
  1212. if method:
  1213. min_loc = mticker.LogitLocator(minor=True)
  1214. ticks = min_loc.tick_values(*lims)
  1215. else:
  1216. ticks = np.array(lims)
  1217. min_form = mticker.LogitFormatter(minor=True)
  1218. for threshold, has_minor in cases:
  1219. min_form.set_minor_threshold(threshold)
  1220. formatted = min_form.format_ticks(ticks)
  1221. labelled = [f for f in formatted if len(f) > 0]
  1222. if has_minor:
  1223. assert len(labelled) > 0, (threshold, has_minor)
  1224. else:
  1225. assert len(labelled) == 0, (threshold, has_minor)
  1226. def test_minor_number(self):
  1227. """
  1228. Test the parameter minor_number
  1229. """
  1230. min_loc = mticker.LogitLocator(minor=True)
  1231. min_form = mticker.LogitFormatter(minor=True)
  1232. ticks = min_loc.tick_values(5e-2, 1 - 5e-2)
  1233. for minor_number in (2, 4, 8, 16):
  1234. min_form.set_minor_number(minor_number)
  1235. formatted = min_form.format_ticks(ticks)
  1236. labelled = [f for f in formatted if len(f) > 0]
  1237. assert len(labelled) == minor_number
  1238. def test_use_overline(self):
  1239. """
  1240. Test the parameter use_overline
  1241. """
  1242. x = 1 - 1e-2
  1243. fx1 = r"$\mathdefault{1-10^{-2}}$"
  1244. fx2 = r"$\mathdefault{\overline{10^{-2}}}$"
  1245. form = mticker.LogitFormatter(use_overline=False)
  1246. assert form(x) == fx1
  1247. form.use_overline(True)
  1248. assert form(x) == fx2
  1249. form.use_overline(False)
  1250. assert form(x) == fx1
  1251. def test_one_half(self):
  1252. """
  1253. Test the parameter one_half
  1254. """
  1255. form = mticker.LogitFormatter()
  1256. assert r"\frac{1}{2}" in form(1/2)
  1257. form.set_one_half("1/2")
  1258. assert "1/2" in form(1/2)
  1259. form.set_one_half("one half")
  1260. assert "one half" in form(1/2)
  1261. @pytest.mark.parametrize("N", (100, 253, 754))
  1262. def test_format_data_short(self, N):
  1263. locs = np.linspace(0, 1, N)[1:-1]
  1264. form = mticker.LogitFormatter()
  1265. for x in locs:
  1266. fx = form.format_data_short(x)
  1267. if fx.startswith("1-"):
  1268. x2 = 1 - float(fx[2:])
  1269. else:
  1270. x2 = float(fx)
  1271. assert abs(x - x2) < 1 / N
  1272. class TestFormatStrFormatter:
  1273. def test_basic(self):
  1274. # test % style formatter
  1275. tmp_form = mticker.FormatStrFormatter('%05d')
  1276. assert '00002' == tmp_form(2)
  1277. class TestStrMethodFormatter:
  1278. test_data = [
  1279. ('{x:05d}', (2,), False, '00002'),
  1280. ('{x:05d}', (2,), True, '00002'),
  1281. ('{x:05d}', (-2,), False, '-0002'),
  1282. ('{x:05d}', (-2,), True, '\N{MINUS SIGN}0002'),
  1283. ('{x:03d}-{pos:02d}', (2, 1), False, '002-01'),
  1284. ('{x:03d}-{pos:02d}', (2, 1), True, '002-01'),
  1285. ('{x:03d}-{pos:02d}', (-2, 1), False, '-02-01'),
  1286. ('{x:03d}-{pos:02d}', (-2, 1), True, '\N{MINUS SIGN}02-01'),
  1287. ]
  1288. @pytest.mark.parametrize('format, input, unicode_minus, expected', test_data)
  1289. def test_basic(self, format, input, unicode_minus, expected):
  1290. with mpl.rc_context({"axes.unicode_minus": unicode_minus}):
  1291. fmt = mticker.StrMethodFormatter(format)
  1292. assert fmt(*input) == expected
  1293. class TestEngFormatter:
  1294. # (unicode_minus, input, expected) where ''expected'' corresponds to the
  1295. # outputs respectively returned when (places=None, places=0, places=2)
  1296. # unicode_minus is a boolean value for the rcParam['axes.unicode_minus']
  1297. raw_format_data = [
  1298. (False, -1234.56789, ('-1.23457 k', '-1 k', '-1.23 k')),
  1299. (True, -1234.56789, ('\N{MINUS SIGN}1.23457 k', '\N{MINUS SIGN}1 k',
  1300. '\N{MINUS SIGN}1.23 k')),
  1301. (False, -1.23456789, ('-1.23457', '-1', '-1.23')),
  1302. (True, -1.23456789, ('\N{MINUS SIGN}1.23457', '\N{MINUS SIGN}1',
  1303. '\N{MINUS SIGN}1.23')),
  1304. (False, -0.123456789, ('-123.457 m', '-123 m', '-123.46 m')),
  1305. (True, -0.123456789, ('\N{MINUS SIGN}123.457 m', '\N{MINUS SIGN}123 m',
  1306. '\N{MINUS SIGN}123.46 m')),
  1307. (False, -0.00123456789, ('-1.23457 m', '-1 m', '-1.23 m')),
  1308. (True, -0.00123456789, ('\N{MINUS SIGN}1.23457 m', '\N{MINUS SIGN}1 m',
  1309. '\N{MINUS SIGN}1.23 m')),
  1310. (True, -0.0, ('0', '0', '0.00')),
  1311. (True, -0, ('0', '0', '0.00')),
  1312. (True, 0, ('0', '0', '0.00')),
  1313. (True, 1.23456789e-6, ('1.23457 µ', '1 µ', '1.23 µ')),
  1314. (True, 0.123456789, ('123.457 m', '123 m', '123.46 m')),
  1315. (True, 0.1, ('100 m', '100 m', '100.00 m')),
  1316. (True, 1, ('1', '1', '1.00')),
  1317. (True, 1.23456789, ('1.23457', '1', '1.23')),
  1318. # places=0: corner-case rounding
  1319. (True, 999.9, ('999.9', '1 k', '999.90')),
  1320. # corner-case rounding for all
  1321. (True, 999.9999, ('1 k', '1 k', '1.00 k')),
  1322. # negative corner-case
  1323. (False, -999.9999, ('-1 k', '-1 k', '-1.00 k')),
  1324. (True, -999.9999, ('\N{MINUS SIGN}1 k', '\N{MINUS SIGN}1 k',
  1325. '\N{MINUS SIGN}1.00 k')),
  1326. (True, 1000, ('1 k', '1 k', '1.00 k')),
  1327. (True, 1001, ('1.001 k', '1 k', '1.00 k')),
  1328. (True, 100001, ('100.001 k', '100 k', '100.00 k')),
  1329. (True, 987654.321, ('987.654 k', '988 k', '987.65 k')),
  1330. # OoR value (> 1000 Q)
  1331. (True, 1.23e33, ('1230 Q', '1230 Q', '1230.00 Q'))
  1332. ]
  1333. @pytest.mark.parametrize('unicode_minus, input, expected', raw_format_data)
  1334. def test_params(self, unicode_minus, input, expected):
  1335. """
  1336. Test the formatting of EngFormatter for various values of the 'places'
  1337. argument, in several cases:
  1338. 0. without a unit symbol but with a (default) space separator;
  1339. 1. with both a unit symbol and a (default) space separator;
  1340. 2. with both a unit symbol and some non default separators;
  1341. 3. without a unit symbol but with some non default separators.
  1342. Note that cases 2. and 3. are looped over several separator strings.
  1343. """
  1344. plt.rcParams['axes.unicode_minus'] = unicode_minus
  1345. UNIT = 's' # seconds
  1346. DIGITS = '0123456789' # %timeit showed 10-20% faster search than set
  1347. # Case 0: unit='' (default) and sep=' ' (default).
  1348. # 'expected' already corresponds to this reference case.
  1349. exp_outputs = expected
  1350. formatters = (
  1351. mticker.EngFormatter(), # places=None (default)
  1352. mticker.EngFormatter(places=0),
  1353. mticker.EngFormatter(places=2)
  1354. )
  1355. for _formatter, _exp_output in zip(formatters, exp_outputs):
  1356. assert _formatter(input) == _exp_output
  1357. # Case 1: unit=UNIT and sep=' ' (default).
  1358. # Append a unit symbol to the reference case.
  1359. # Beware of the values in [1, 1000), where there is no prefix!
  1360. exp_outputs = (_s + " " + UNIT if _s[-1] in DIGITS # case w/o prefix
  1361. else _s + UNIT for _s in expected)
  1362. formatters = (
  1363. mticker.EngFormatter(unit=UNIT), # places=None (default)
  1364. mticker.EngFormatter(unit=UNIT, places=0),
  1365. mticker.EngFormatter(unit=UNIT, places=2)
  1366. )
  1367. for _formatter, _exp_output in zip(formatters, exp_outputs):
  1368. assert _formatter(input) == _exp_output
  1369. # Test several non default separators: no separator, a narrow
  1370. # no-break space (Unicode character) and an extravagant string.
  1371. for _sep in ("", "\N{NARROW NO-BREAK SPACE}", "@_@"):
  1372. # Case 2: unit=UNIT and sep=_sep.
  1373. # Replace the default space separator from the reference case
  1374. # with the tested one `_sep` and append a unit symbol to it.
  1375. exp_outputs = (_s + _sep + UNIT if _s[-1] in DIGITS # no prefix
  1376. else _s.replace(" ", _sep) + UNIT
  1377. for _s in expected)
  1378. formatters = (
  1379. mticker.EngFormatter(unit=UNIT, sep=_sep), # places=None
  1380. mticker.EngFormatter(unit=UNIT, places=0, sep=_sep),
  1381. mticker.EngFormatter(unit=UNIT, places=2, sep=_sep)
  1382. )
  1383. for _formatter, _exp_output in zip(formatters, exp_outputs):
  1384. assert _formatter(input) == _exp_output
  1385. # Case 3: unit='' (default) and sep=_sep.
  1386. # Replace the default space separator from the reference case
  1387. # with the tested one `_sep`. Reference case is already unitless.
  1388. exp_outputs = (_s.replace(" ", _sep) for _s in expected)
  1389. formatters = (
  1390. mticker.EngFormatter(sep=_sep), # places=None (default)
  1391. mticker.EngFormatter(places=0, sep=_sep),
  1392. mticker.EngFormatter(places=2, sep=_sep)
  1393. )
  1394. for _formatter, _exp_output in zip(formatters, exp_outputs):
  1395. assert _formatter(input) == _exp_output
  1396. def test_engformatter_usetex_useMathText():
  1397. fig, ax = plt.subplots()
  1398. ax.plot([0, 500, 1000], [0, 500, 1000])
  1399. ax.set_xticks([0, 500, 1000])
  1400. for formatter in (mticker.EngFormatter(usetex=True),
  1401. mticker.EngFormatter(useMathText=True)):
  1402. ax.xaxis.set_major_formatter(formatter)
  1403. fig.canvas.draw()
  1404. x_tick_label_text = [labl.get_text() for labl in ax.get_xticklabels()]
  1405. # Checking if the dollar `$` signs have been inserted around numbers
  1406. # in tick labels.
  1407. assert x_tick_label_text == ['$0$', '$500$', '$1$ k']
  1408. @pytest.mark.parametrize(
  1409. 'data_offset, noise, oom_center_desired, oom_noise_desired', [
  1410. (271_490_000_000.0, 10, 9, 0),
  1411. (27_149_000_000_000.0, 10_000_000, 12, 6),
  1412. (27.149, 0.01, 0, -3),
  1413. (2_714.9, 0.01, 3, -3),
  1414. (271_490.0, 0.001, 3, -3),
  1415. (271.49, 0.001, 0, -3),
  1416. # The following sets of parameters demonstrates that when
  1417. # oom(data_offset)-1 and oom(noise)-2 equal a standard 3*N oom, we get
  1418. # that oom_noise_desired < oom(noise)
  1419. (27_149_000_000.0, 100, 9, +3),
  1420. (27.149, 1e-07, 0, -6),
  1421. (271.49, 0.0001, 0, -3),
  1422. (27.149, 0.0001, 0, -3),
  1423. # Tests where oom(data_offset) <= oom(noise), those are probably
  1424. # covered by the part where formatter.offset != 0
  1425. (27_149.0, 10_000, 0, 3),
  1426. (27.149, 10_000, 0, 3),
  1427. (27.149, 1_000, 0, 3),
  1428. (27.149, 100, 0, 0),
  1429. (27.149, 10, 0, 0),
  1430. ]
  1431. )
  1432. def test_engformatter_offset_oom(
  1433. data_offset,
  1434. noise,
  1435. oom_center_desired,
  1436. oom_noise_desired
  1437. ):
  1438. UNIT = "eV"
  1439. fig, ax = plt.subplots()
  1440. ydata = data_offset + np.arange(-5, 7, dtype=float)*noise
  1441. ax.plot(ydata)
  1442. formatter = mticker.EngFormatter(useOffset=True, unit=UNIT)
  1443. # So that offset strings will always have the same size
  1444. formatter.ENG_PREFIXES[0] = "_"
  1445. ax.yaxis.set_major_formatter(formatter)
  1446. fig.canvas.draw()
  1447. offset_got = formatter.get_offset()
  1448. ticks_got = [labl.get_text() for labl in ax.get_yticklabels()]
  1449. # Predicting whether offset should be 0 or not is essentially testing
  1450. # ScalarFormatter._compute_offset . This function is pretty complex and it
  1451. # would be nice to test it, but this is out of scope for this test which
  1452. # only makes sure that offset text and the ticks gets the correct unit
  1453. # prefixes and the ticks.
  1454. if formatter.offset:
  1455. prefix_noise_got = offset_got[2]
  1456. prefix_noise_desired = formatter.ENG_PREFIXES[oom_noise_desired]
  1457. prefix_center_got = offset_got[-1-len(UNIT)]
  1458. prefix_center_desired = formatter.ENG_PREFIXES[oom_center_desired]
  1459. assert prefix_noise_desired == prefix_noise_got
  1460. assert prefix_center_desired == prefix_center_got
  1461. # Make sure the ticks didn't get the UNIT
  1462. for tick in ticks_got:
  1463. assert UNIT not in tick
  1464. else:
  1465. assert oom_center_desired == 0
  1466. assert offset_got == ""
  1467. # Make sure the ticks contain now the prefixes
  1468. for tick in ticks_got:
  1469. # 0 is zero on all orders of magnitudes, no matter what is
  1470. # oom_noise_desired
  1471. prefix_idx = 0 if tick[0] == "0" else oom_noise_desired
  1472. assert tick.endswith(formatter.ENG_PREFIXES[prefix_idx] + UNIT)
  1473. class TestPercentFormatter:
  1474. percent_data = [
  1475. # Check explicitly set decimals over different intervals and values
  1476. (100, 0, '%', 120, 100, '120%'),
  1477. (100, 0, '%', 100, 90, '100%'),
  1478. (100, 0, '%', 90, 50, '90%'),
  1479. (100, 0, '%', -1.7, 40, '-2%'),
  1480. (100, 1, '%', 90.0, 100, '90.0%'),
  1481. (100, 1, '%', 80.1, 90, '80.1%'),
  1482. (100, 1, '%', 70.23, 50, '70.2%'),
  1483. # 60.554 instead of 60.55: see https://bugs.python.org/issue5118
  1484. (100, 1, '%', -60.554, 40, '-60.6%'),
  1485. # Check auto decimals over different intervals and values
  1486. (100, None, '%', 95, 1, '95.00%'),
  1487. (1.0, None, '%', 3, 6, '300%'),
  1488. (17.0, None, '%', 1, 8.5, '6%'),
  1489. (17.0, None, '%', 1, 8.4, '5.9%'),
  1490. (5, None, '%', -100, 0.000001, '-2000.00000%'),
  1491. # Check percent symbol
  1492. (1.0, 2, None, 1.2, 100, '120.00'),
  1493. (75, 3, '', 50, 100, '66.667'),
  1494. (42, None, '^^Foobar$$', 21, 12, '50.0^^Foobar$$'),
  1495. ]
  1496. percent_ids = [
  1497. # Check explicitly set decimals over different intervals and values
  1498. 'decimals=0, x>100%',
  1499. 'decimals=0, x=100%',
  1500. 'decimals=0, x<100%',
  1501. 'decimals=0, x<0%',
  1502. 'decimals=1, x>100%',
  1503. 'decimals=1, x=100%',
  1504. 'decimals=1, x<100%',
  1505. 'decimals=1, x<0%',
  1506. # Check auto decimals over different intervals and values
  1507. 'autodecimal, x<100%, display_range=1',
  1508. 'autodecimal, x>100%, display_range=6 (custom xmax test)',
  1509. 'autodecimal, x<100%, display_range=8.5 (autodecimal test 1)',
  1510. 'autodecimal, x<100%, display_range=8.4 (autodecimal test 2)',
  1511. 'autodecimal, x<-100%, display_range=1e-6 (tiny display range)',
  1512. # Check percent symbol
  1513. 'None as percent symbol',
  1514. 'Empty percent symbol',
  1515. 'Custom percent symbol',
  1516. ]
  1517. latex_data = [
  1518. (False, False, r'50\{t}%'),
  1519. (False, True, r'50\\\{t\}\%'),
  1520. (True, False, r'50\{t}%'),
  1521. (True, True, r'50\{t}%'),
  1522. ]
  1523. @pytest.mark.parametrize(
  1524. 'xmax, decimals, symbol, x, display_range, expected',
  1525. percent_data, ids=percent_ids)
  1526. def test_basic(self, xmax, decimals, symbol,
  1527. x, display_range, expected):
  1528. formatter = mticker.PercentFormatter(xmax, decimals, symbol)
  1529. with mpl.rc_context(rc={'text.usetex': False}):
  1530. assert formatter.format_pct(x, display_range) == expected
  1531. @pytest.mark.parametrize('is_latex, usetex, expected', latex_data)
  1532. def test_latex(self, is_latex, usetex, expected):
  1533. fmt = mticker.PercentFormatter(symbol='\\{t}%', is_latex=is_latex)
  1534. with mpl.rc_context(rc={'text.usetex': usetex}):
  1535. assert fmt.format_pct(50, 100) == expected
  1536. def _impl_locale_comma():
  1537. try:
  1538. locale.setlocale(locale.LC_ALL, 'de_DE.UTF-8')
  1539. except locale.Error:
  1540. print('SKIP: Locale de_DE.UTF-8 is not supported on this machine')
  1541. return
  1542. ticks = mticker.ScalarFormatter(useMathText=True, useLocale=True)
  1543. fmt = '$\\mathdefault{%1.1f}$'
  1544. x = ticks._format_maybe_minus_and_locale(fmt, 0.5)
  1545. assert x == '$\\mathdefault{0{,}5}$'
  1546. # Do not change , in the format string
  1547. fmt = ',$\\mathdefault{,%1.1f},$'
  1548. x = ticks._format_maybe_minus_and_locale(fmt, 0.5)
  1549. assert x == ',$\\mathdefault{,0{,}5},$'
  1550. # Make sure no brackets are added if not using math text
  1551. ticks = mticker.ScalarFormatter(useMathText=False, useLocale=True)
  1552. fmt = '%1.1f'
  1553. x = ticks._format_maybe_minus_and_locale(fmt, 0.5)
  1554. assert x == '0,5'
  1555. def test_locale_comma():
  1556. # On some systems/pytest versions, `pytest.skip` in an exception handler
  1557. # does not skip, but is treated as an exception, so directly running this
  1558. # test can incorrectly fail instead of skip.
  1559. # Instead, run this test in a subprocess, which avoids the problem, and the
  1560. # need to fix the locale after.
  1561. proc = mpl.testing.subprocess_run_helper(_impl_locale_comma, timeout=60,
  1562. extra_env={'MPLBACKEND': 'Agg'})
  1563. skip_msg = next((line[len('SKIP:'):].strip()
  1564. for line in proc.stdout.splitlines()
  1565. if line.startswith('SKIP:')),
  1566. '')
  1567. if skip_msg:
  1568. pytest.skip(skip_msg)
  1569. def test_majformatter_type():
  1570. fig, ax = plt.subplots()
  1571. with pytest.raises(TypeError):
  1572. ax.xaxis.set_major_formatter(mticker.LogLocator())
  1573. def test_minformatter_type():
  1574. fig, ax = plt.subplots()
  1575. with pytest.raises(TypeError):
  1576. ax.xaxis.set_minor_formatter(mticker.LogLocator())
  1577. def test_majlocator_type():
  1578. fig, ax = plt.subplots()
  1579. with pytest.raises(TypeError):
  1580. ax.xaxis.set_major_locator(mticker.LogFormatter())
  1581. def test_minlocator_type():
  1582. fig, ax = plt.subplots()
  1583. with pytest.raises(TypeError):
  1584. ax.xaxis.set_minor_locator(mticker.LogFormatter())
  1585. def test_minorticks_rc():
  1586. fig = plt.figure()
  1587. def minorticksubplot(xminor, yminor, i):
  1588. rc = {'xtick.minor.visible': xminor,
  1589. 'ytick.minor.visible': yminor}
  1590. with plt.rc_context(rc=rc):
  1591. ax = fig.add_subplot(2, 2, i)
  1592. assert (len(ax.xaxis.get_minor_ticks()) > 0) == xminor
  1593. assert (len(ax.yaxis.get_minor_ticks()) > 0) == yminor
  1594. minorticksubplot(False, False, 1)
  1595. minorticksubplot(True, False, 2)
  1596. minorticksubplot(False, True, 3)
  1597. minorticksubplot(True, True, 4)
  1598. def test_minorticks_toggle():
  1599. """
  1600. Test toggling minor ticks
  1601. Test `.Axis.minorticks_on()` and `.Axis.minorticks_off()`. Testing is
  1602. limited to a subset of built-in scales - `'linear'`, `'log'`, `'asinh'`
  1603. and `'logit'`. `symlog` scale does not seem to have a working minor
  1604. locator and is omitted. In future, this test should cover all scales in
  1605. `matplotlib.scale.get_scale_names()`.
  1606. """
  1607. fig = plt.figure()
  1608. def minortickstoggle(xminor, yminor, scale, i):
  1609. ax = fig.add_subplot(2, 2, i)
  1610. ax.set_xscale(scale)
  1611. ax.set_yscale(scale)
  1612. if not xminor and not yminor:
  1613. ax.minorticks_off()
  1614. if xminor and not yminor:
  1615. ax.xaxis.minorticks_on()
  1616. ax.yaxis.minorticks_off()
  1617. if not xminor and yminor:
  1618. ax.xaxis.minorticks_off()
  1619. ax.yaxis.minorticks_on()
  1620. if xminor and yminor:
  1621. ax.minorticks_on()
  1622. assert (len(ax.xaxis.get_minor_ticks()) > 0) == xminor
  1623. assert (len(ax.yaxis.get_minor_ticks()) > 0) == yminor
  1624. scales = ['linear', 'log', 'asinh', 'logit']
  1625. for scale in scales:
  1626. minortickstoggle(False, False, scale, 1)
  1627. minortickstoggle(True, False, scale, 2)
  1628. minortickstoggle(False, True, scale, 3)
  1629. minortickstoggle(True, True, scale, 4)
  1630. fig.clear()
  1631. plt.close(fig)
  1632. @pytest.mark.parametrize('remove_overlapping_locs, expected_num',
  1633. ((True, 6),
  1634. (None, 6), # this tests the default
  1635. (False, 9)))
  1636. def test_remove_overlap(remove_overlapping_locs, expected_num):
  1637. t = np.arange("2018-11-03", "2018-11-06", dtype="datetime64")
  1638. x = np.ones(len(t))
  1639. fig, ax = plt.subplots()
  1640. ax.plot(t, x)
  1641. ax.xaxis.set_major_locator(mpl.dates.DayLocator())
  1642. ax.xaxis.set_major_formatter(mpl.dates.DateFormatter('\n%a'))
  1643. ax.xaxis.set_minor_locator(mpl.dates.HourLocator((0, 6, 12, 18)))
  1644. ax.xaxis.set_minor_formatter(mpl.dates.DateFormatter('%H:%M'))
  1645. # force there to be extra ticks
  1646. ax.xaxis.get_minor_ticks(15)
  1647. if remove_overlapping_locs is not None:
  1648. ax.xaxis.remove_overlapping_locs = remove_overlapping_locs
  1649. # check that getter/setter exists
  1650. current = ax.xaxis.remove_overlapping_locs
  1651. assert (current == ax.xaxis.get_remove_overlapping_locs())
  1652. plt.setp(ax.xaxis, remove_overlapping_locs=current)
  1653. new = ax.xaxis.remove_overlapping_locs
  1654. assert (new == ax.xaxis.remove_overlapping_locs)
  1655. # check that the accessors filter correctly
  1656. # this is the method that does the actual filtering
  1657. assert len(ax.xaxis.get_minorticklocs()) == expected_num
  1658. # these three are derivative
  1659. assert len(ax.xaxis.get_minor_ticks()) == expected_num
  1660. assert len(ax.xaxis.get_minorticklabels()) == expected_num
  1661. assert len(ax.xaxis.get_minorticklines()) == expected_num*2
  1662. @pytest.mark.parametrize('sub', [
  1663. ['hi', 'aardvark'],
  1664. np.zeros((2, 2))])
  1665. def test_bad_locator_subs(sub):
  1666. ll = mticker.LogLocator()
  1667. with pytest.raises(ValueError):
  1668. ll.set_params(subs=sub)
  1669. @pytest.mark.parametrize('numticks', [1, 2, 3, 9])
  1670. @mpl.style.context('default')
  1671. def test_small_range_loglocator(numticks):
  1672. ll = mticker.LogLocator()
  1673. ll.set_params(numticks=numticks)
  1674. for top in [5, 7, 9, 11, 15, 50, 100, 1000]:
  1675. ticks = ll.tick_values(.5, top)
  1676. assert (np.diff(np.log10(ll.tick_values(6, 150))) == 1).all()
  1677. def test_NullFormatter():
  1678. formatter = mticker.NullFormatter()
  1679. assert formatter(1.0) == ''
  1680. assert formatter.format_data(1.0) == ''
  1681. assert formatter.format_data_short(1.0) == ''
  1682. @pytest.mark.parametrize('formatter', (
  1683. mticker.FuncFormatter(lambda a: f'val: {a}'),
  1684. mticker.FixedFormatter(('foo', 'bar'))))
  1685. def test_set_offset_string(formatter):
  1686. assert formatter.get_offset() == ''
  1687. formatter.set_offset_string('mpl')
  1688. assert formatter.get_offset() == 'mpl'
  1689. def test_minorticks_on_multi_fig():
  1690. """
  1691. Turning on minor gridlines in a multi-Axes Figure
  1692. that contains more than one boxplot and shares the x-axis
  1693. should not raise an exception.
  1694. """
  1695. fig, ax = plt.subplots()
  1696. ax.boxplot(np.arange(10), positions=[0])
  1697. ax.boxplot(np.arange(10), positions=[0])
  1698. ax.boxplot(np.arange(10), positions=[1])
  1699. ax.grid(which="major")
  1700. ax.grid(which="minor")
  1701. ax.minorticks_on()
  1702. fig.draw_without_rendering()
  1703. assert ax.get_xgridlines()
  1704. assert isinstance(ax.xaxis.get_minor_locator(), mpl.ticker.AutoMinorLocator)