ticker.py 105 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990
  1. """
  2. Tick locating and formatting
  3. ============================
  4. This module contains classes for configuring tick locating and formatting.
  5. Generic tick locators and formatters are provided, as well as domain specific
  6. custom ones.
  7. Although the locators know nothing about major or minor ticks, they are used
  8. by the Axis class to support major and minor tick locating and formatting.
  9. .. _tick_locating:
  10. .. _locators:
  11. Tick locating
  12. -------------
  13. The Locator class is the base class for all tick locators. The locators
  14. handle autoscaling of the view limits based on the data limits, and the
  15. choosing of tick locations. A useful semi-automatic tick locator is
  16. `MultipleLocator`. It is initialized with a base, e.g., 10, and it picks
  17. axis limits and ticks that are multiples of that base.
  18. The Locator subclasses defined here are:
  19. ======================= =======================================================
  20. `AutoLocator` `MaxNLocator` with simple defaults. This is the default
  21. tick locator for most plotting.
  22. `MaxNLocator` Finds up to a max number of intervals with ticks at
  23. nice locations.
  24. `LinearLocator` Space ticks evenly from min to max.
  25. `LogLocator` Space ticks logarithmically from min to max.
  26. `MultipleLocator` Ticks and range are a multiple of base; either integer
  27. or float.
  28. `FixedLocator` Tick locations are fixed.
  29. `IndexLocator` Locator for index plots (e.g., where
  30. ``x = range(len(y))``).
  31. `NullLocator` No ticks.
  32. `SymmetricalLogLocator` Locator for use with the symlog norm; works like
  33. `LogLocator` for the part outside of the threshold and
  34. adds 0 if inside the limits.
  35. `AsinhLocator` Locator for use with the asinh norm, attempting to
  36. space ticks approximately uniformly.
  37. `LogitLocator` Locator for logit scaling.
  38. `AutoMinorLocator` Locator for minor ticks when the axis is linear and the
  39. major ticks are uniformly spaced. Subdivides the major
  40. tick interval into a specified number of minor
  41. intervals, defaulting to 4 or 5 depending on the major
  42. interval.
  43. ======================= =======================================================
  44. There are a number of locators specialized for date locations - see
  45. the :mod:`.dates` module.
  46. You can define your own locator by deriving from Locator. You must
  47. override the ``__call__`` method, which returns a sequence of locations,
  48. and you will probably want to override the autoscale method to set the
  49. view limits from the data limits.
  50. If you want to override the default locator, use one of the above or a custom
  51. locator and pass it to the x- or y-axis instance. The relevant methods are::
  52. ax.xaxis.set_major_locator(xmajor_locator)
  53. ax.xaxis.set_minor_locator(xminor_locator)
  54. ax.yaxis.set_major_locator(ymajor_locator)
  55. ax.yaxis.set_minor_locator(yminor_locator)
  56. The default minor locator is `NullLocator`, i.e., no minor ticks on by default.
  57. .. note::
  58. `Locator` instances should not be used with more than one
  59. `~matplotlib.axis.Axis` or `~matplotlib.axes.Axes`. So instead of::
  60. locator = MultipleLocator(5)
  61. ax.xaxis.set_major_locator(locator)
  62. ax2.xaxis.set_major_locator(locator)
  63. do the following instead::
  64. ax.xaxis.set_major_locator(MultipleLocator(5))
  65. ax2.xaxis.set_major_locator(MultipleLocator(5))
  66. .. _formatters:
  67. Tick formatting
  68. ---------------
  69. Tick formatting is controlled by classes derived from Formatter. The formatter
  70. operates on a single tick value and returns a string to the axis.
  71. ========================= =====================================================
  72. `NullFormatter` No labels on the ticks.
  73. `FixedFormatter` Set the strings manually for the labels.
  74. `FuncFormatter` User defined function sets the labels.
  75. `StrMethodFormatter` Use string `format` method.
  76. `FormatStrFormatter` Use an old-style sprintf format string.
  77. `ScalarFormatter` Default formatter for scalars: autopick the format
  78. string.
  79. `LogFormatter` Formatter for log axes.
  80. `LogFormatterExponent` Format values for log axis using
  81. ``exponent = log_base(value)``.
  82. `LogFormatterMathtext` Format values for log axis using
  83. ``exponent = log_base(value)`` using Math text.
  84. `LogFormatterSciNotation` Format values for log axis using scientific notation.
  85. `LogitFormatter` Probability formatter.
  86. `EngFormatter` Format labels in engineering notation.
  87. `PercentFormatter` Format labels as a percentage.
  88. ========================= =====================================================
  89. You can derive your own formatter from the Formatter base class by
  90. simply overriding the ``__call__`` method. The formatter class has
  91. access to the axis view and data limits.
  92. To control the major and minor tick label formats, use one of the
  93. following methods::
  94. ax.xaxis.set_major_formatter(xmajor_formatter)
  95. ax.xaxis.set_minor_formatter(xminor_formatter)
  96. ax.yaxis.set_major_formatter(ymajor_formatter)
  97. ax.yaxis.set_minor_formatter(yminor_formatter)
  98. In addition to a `.Formatter` instance, `~.Axis.set_major_formatter` and
  99. `~.Axis.set_minor_formatter` also accept a ``str`` or function. ``str`` input
  100. will be internally replaced with an autogenerated `.StrMethodFormatter` with
  101. the input ``str``. For function input, a `.FuncFormatter` with the input
  102. function will be generated and used.
  103. See :doc:`/gallery/ticks/major_minor_demo` for an example of setting major
  104. and minor ticks. See the :mod:`matplotlib.dates` module for more information
  105. and examples of using date locators and formatters.
  106. """
  107. import itertools
  108. import logging
  109. import locale
  110. import math
  111. from numbers import Integral
  112. import string
  113. import numpy as np
  114. import matplotlib as mpl
  115. from matplotlib import _api, cbook
  116. from matplotlib import transforms as mtransforms
  117. _log = logging.getLogger(__name__)
  118. __all__ = ('TickHelper', 'Formatter', 'FixedFormatter',
  119. 'NullFormatter', 'FuncFormatter', 'FormatStrFormatter',
  120. 'StrMethodFormatter', 'ScalarFormatter', 'LogFormatter',
  121. 'LogFormatterExponent', 'LogFormatterMathtext',
  122. 'LogFormatterSciNotation',
  123. 'LogitFormatter', 'EngFormatter', 'PercentFormatter',
  124. 'Locator', 'IndexLocator', 'FixedLocator', 'NullLocator',
  125. 'LinearLocator', 'LogLocator', 'AutoLocator',
  126. 'MultipleLocator', 'MaxNLocator', 'AutoMinorLocator',
  127. 'SymmetricalLogLocator', 'AsinhLocator', 'LogitLocator')
  128. class _DummyAxis:
  129. __name__ = "dummy"
  130. def __init__(self, minpos=0):
  131. self._data_interval = (0, 1)
  132. self._view_interval = (0, 1)
  133. self._minpos = minpos
  134. def get_view_interval(self):
  135. return self._view_interval
  136. def set_view_interval(self, vmin, vmax):
  137. self._view_interval = (vmin, vmax)
  138. def get_minpos(self):
  139. return self._minpos
  140. def get_data_interval(self):
  141. return self._data_interval
  142. def set_data_interval(self, vmin, vmax):
  143. self._data_interval = (vmin, vmax)
  144. def get_tick_space(self):
  145. # Just use the long-standing default of nbins==9
  146. return 9
  147. class TickHelper:
  148. axis = None
  149. def set_axis(self, axis):
  150. self.axis = axis
  151. def create_dummy_axis(self, **kwargs):
  152. if self.axis is None:
  153. self.axis = _DummyAxis(**kwargs)
  154. class Formatter(TickHelper):
  155. """
  156. Create a string based on a tick value and location.
  157. """
  158. # some classes want to see all the locs to help format
  159. # individual ones
  160. locs = []
  161. def __call__(self, x, pos=None):
  162. """
  163. Return the format for tick value *x* at position pos.
  164. ``pos=None`` indicates an unspecified location.
  165. """
  166. raise NotImplementedError('Derived must override')
  167. def format_ticks(self, values):
  168. """Return the tick labels for all the ticks at once."""
  169. self.set_locs(values)
  170. return [self(value, i) for i, value in enumerate(values)]
  171. def format_data(self, value):
  172. """
  173. Return the full string representation of the value with the
  174. position unspecified.
  175. """
  176. return self.__call__(value)
  177. def format_data_short(self, value):
  178. """
  179. Return a short string version of the tick value.
  180. Defaults to the position-independent long value.
  181. """
  182. return self.format_data(value)
  183. def get_offset(self):
  184. return ''
  185. def set_locs(self, locs):
  186. """
  187. Set the locations of the ticks.
  188. This method is called before computing the tick labels because some
  189. formatters need to know all tick locations to do so.
  190. """
  191. self.locs = locs
  192. @staticmethod
  193. def fix_minus(s):
  194. """
  195. Some classes may want to replace a hyphen for minus with the proper
  196. Unicode symbol (U+2212) for typographical correctness. This is a
  197. helper method to perform such a replacement when it is enabled via
  198. :rc:`axes.unicode_minus`.
  199. """
  200. return (s.replace('-', '\N{MINUS SIGN}')
  201. if mpl.rcParams['axes.unicode_minus']
  202. else s)
  203. def _set_locator(self, locator):
  204. """Subclasses may want to override this to set a locator."""
  205. pass
  206. class NullFormatter(Formatter):
  207. """Always return the empty string."""
  208. def __call__(self, x, pos=None):
  209. # docstring inherited
  210. return ''
  211. class FixedFormatter(Formatter):
  212. """
  213. Return fixed strings for tick labels based only on position, not value.
  214. .. note::
  215. `.FixedFormatter` should only be used together with `.FixedLocator`.
  216. Otherwise, the labels may end up in unexpected positions.
  217. """
  218. def __init__(self, seq):
  219. """Set the sequence *seq* of strings that will be used for labels."""
  220. self.seq = seq
  221. self.offset_string = ''
  222. def __call__(self, x, pos=None):
  223. """
  224. Return the label that matches the position, regardless of the value.
  225. For positions ``pos < len(seq)``, return ``seq[i]`` regardless of
  226. *x*. Otherwise return empty string. ``seq`` is the sequence of
  227. strings that this object was initialized with.
  228. """
  229. if pos is None or pos >= len(self.seq):
  230. return ''
  231. else:
  232. return self.seq[pos]
  233. def get_offset(self):
  234. return self.offset_string
  235. def set_offset_string(self, ofs):
  236. self.offset_string = ofs
  237. class FuncFormatter(Formatter):
  238. """
  239. Use a user-defined function for formatting.
  240. The function should take in two inputs (a tick value ``x`` and a
  241. position ``pos``), and return a string containing the corresponding
  242. tick label.
  243. """
  244. def __init__(self, func):
  245. self.func = func
  246. self.offset_string = ""
  247. def __call__(self, x, pos=None):
  248. """
  249. Return the value of the user defined function.
  250. *x* and *pos* are passed through as-is.
  251. """
  252. return self.func(x, pos)
  253. def get_offset(self):
  254. return self.offset_string
  255. def set_offset_string(self, ofs):
  256. self.offset_string = ofs
  257. class FormatStrFormatter(Formatter):
  258. """
  259. Use an old-style ('%' operator) format string to format the tick.
  260. The format string should have a single variable format (%) in it.
  261. It will be applied to the value (not the position) of the tick.
  262. Negative numeric values (e.g., -1) will use a dash, not a Unicode minus;
  263. use mathtext to get a Unicode minus by wrapping the format specifier with $
  264. (e.g. "$%g$").
  265. """
  266. def __init__(self, fmt):
  267. self.fmt = fmt
  268. def __call__(self, x, pos=None):
  269. """
  270. Return the formatted label string.
  271. Only the value *x* is formatted. The position is ignored.
  272. """
  273. return self.fmt % x
  274. class _UnicodeMinusFormat(string.Formatter):
  275. """
  276. A specialized string formatter so that `.StrMethodFormatter` respects
  277. :rc:`axes.unicode_minus`. This implementation relies on the fact that the
  278. format string is only ever called with kwargs *x* and *pos*, so it blindly
  279. replaces dashes by unicode minuses without further checking.
  280. """
  281. def format_field(self, value, format_spec):
  282. return Formatter.fix_minus(super().format_field(value, format_spec))
  283. class StrMethodFormatter(Formatter):
  284. """
  285. Use a new-style format string (as used by `str.format`) to format the tick.
  286. The field used for the tick value must be labeled *x* and the field used
  287. for the tick position must be labeled *pos*.
  288. The formatter will respect :rc:`axes.unicode_minus` when formatting
  289. negative numeric values.
  290. It is typically unnecessary to explicitly construct `.StrMethodFormatter`
  291. objects, as `~.Axis.set_major_formatter` directly accepts the format string
  292. itself.
  293. """
  294. def __init__(self, fmt):
  295. self.fmt = fmt
  296. def __call__(self, x, pos=None):
  297. """
  298. Return the formatted label string.
  299. *x* and *pos* are passed to `str.format` as keyword arguments
  300. with those exact names.
  301. """
  302. return _UnicodeMinusFormat().format(self.fmt, x=x, pos=pos)
  303. class ScalarFormatter(Formatter):
  304. """
  305. Format tick values as a number.
  306. Parameters
  307. ----------
  308. useOffset : bool or float, default: :rc:`axes.formatter.useoffset`
  309. Whether to use offset notation. See `.set_useOffset`.
  310. useMathText : bool, default: :rc:`axes.formatter.use_mathtext`
  311. Whether to use fancy math formatting. See `.set_useMathText`.
  312. useLocale : bool, default: :rc:`axes.formatter.use_locale`.
  313. Whether to use locale settings for decimal sign and positive sign.
  314. See `.set_useLocale`.
  315. usetex : bool, default: :rc:`text.usetex`
  316. To enable/disable the use of TeX's math mode for rendering the
  317. numbers in the formatter.
  318. .. versionadded:: 3.10
  319. Notes
  320. -----
  321. In addition to the parameters above, the formatting of scientific vs.
  322. floating point representation can be configured via `.set_scientific`
  323. and `.set_powerlimits`).
  324. **Offset notation and scientific notation**
  325. Offset notation and scientific notation look quite similar at first sight.
  326. Both split some information from the formatted tick values and display it
  327. at the end of the axis.
  328. - The scientific notation splits up the order of magnitude, i.e. a
  329. multiplicative scaling factor, e.g. ``1e6``.
  330. - The offset notation separates an additive constant, e.g. ``+1e6``. The
  331. offset notation label is always prefixed with a ``+`` or ``-`` sign
  332. and is thus distinguishable from the order of magnitude label.
  333. The following plot with x limits ``1_000_000`` to ``1_000_010`` illustrates
  334. the different formatting. Note the labels at the right edge of the x axis.
  335. .. plot::
  336. lim = (1_000_000, 1_000_010)
  337. fig, (ax1, ax2, ax3) = plt.subplots(3, 1, gridspec_kw={'hspace': 2})
  338. ax1.set(title='offset notation', xlim=lim)
  339. ax2.set(title='scientific notation', xlim=lim)
  340. ax2.xaxis.get_major_formatter().set_useOffset(False)
  341. ax3.set(title='floating-point notation', xlim=lim)
  342. ax3.xaxis.get_major_formatter().set_useOffset(False)
  343. ax3.xaxis.get_major_formatter().set_scientific(False)
  344. """
  345. def __init__(self, useOffset=None, useMathText=None, useLocale=None, *,
  346. usetex=None):
  347. if useOffset is None:
  348. useOffset = mpl.rcParams['axes.formatter.useoffset']
  349. self._offset_threshold = \
  350. mpl.rcParams['axes.formatter.offset_threshold']
  351. self.set_useOffset(useOffset)
  352. self.set_usetex(usetex)
  353. self.set_useMathText(useMathText)
  354. self.orderOfMagnitude = 0
  355. self.format = ''
  356. self._scientific = True
  357. self._powerlimits = mpl.rcParams['axes.formatter.limits']
  358. self.set_useLocale(useLocale)
  359. def get_usetex(self):
  360. """Return whether TeX's math mode is enabled for rendering."""
  361. return self._usetex
  362. def set_usetex(self, val):
  363. """Set whether to use TeX's math mode for rendering numbers in the formatter."""
  364. self._usetex = mpl._val_or_rc(val, 'text.usetex')
  365. usetex = property(fget=get_usetex, fset=set_usetex)
  366. def get_useOffset(self):
  367. """
  368. Return whether automatic mode for offset notation is active.
  369. This returns True if ``set_useOffset(True)``; it returns False if an
  370. explicit offset was set, e.g. ``set_useOffset(1000)``.
  371. See Also
  372. --------
  373. ScalarFormatter.set_useOffset
  374. """
  375. return self._useOffset
  376. def set_useOffset(self, val):
  377. """
  378. Set whether to use offset notation.
  379. When formatting a set numbers whose value is large compared to their
  380. range, the formatter can separate an additive constant. This can
  381. shorten the formatted numbers so that they are less likely to overlap
  382. when drawn on an axis.
  383. Parameters
  384. ----------
  385. val : bool or float
  386. - If False, do not use offset notation.
  387. - If True (=automatic mode), use offset notation if it can make
  388. the residual numbers significantly shorter. The exact behavior
  389. is controlled by :rc:`axes.formatter.offset_threshold`.
  390. - If a number, force an offset of the given value.
  391. Examples
  392. --------
  393. With active offset notation, the values
  394. ``100_000, 100_002, 100_004, 100_006, 100_008``
  395. will be formatted as ``0, 2, 4, 6, 8`` plus an offset ``+1e5``, which
  396. is written to the edge of the axis.
  397. """
  398. if val in [True, False]:
  399. self.offset = 0
  400. self._useOffset = val
  401. else:
  402. self._useOffset = False
  403. self.offset = val
  404. useOffset = property(fget=get_useOffset, fset=set_useOffset)
  405. def get_useLocale(self):
  406. """
  407. Return whether locale settings are used for formatting.
  408. See Also
  409. --------
  410. ScalarFormatter.set_useLocale
  411. """
  412. return self._useLocale
  413. def set_useLocale(self, val):
  414. """
  415. Set whether to use locale settings for decimal sign and positive sign.
  416. Parameters
  417. ----------
  418. val : bool or None
  419. *None* resets to :rc:`axes.formatter.use_locale`.
  420. """
  421. if val is None:
  422. self._useLocale = mpl.rcParams['axes.formatter.use_locale']
  423. else:
  424. self._useLocale = val
  425. useLocale = property(fget=get_useLocale, fset=set_useLocale)
  426. def _format_maybe_minus_and_locale(self, fmt, arg):
  427. """
  428. Format *arg* with *fmt*, applying Unicode minus and locale if desired.
  429. """
  430. return self.fix_minus(
  431. # Escape commas introduced by locale.format_string if using math text,
  432. # but not those present from the beginning in fmt.
  433. (",".join(locale.format_string(part, (arg,), True).replace(",", "{,}")
  434. for part in fmt.split(",")) if self._useMathText
  435. else locale.format_string(fmt, (arg,), True))
  436. if self._useLocale
  437. else fmt % arg)
  438. def get_useMathText(self):
  439. """
  440. Return whether to use fancy math formatting.
  441. See Also
  442. --------
  443. ScalarFormatter.set_useMathText
  444. """
  445. return self._useMathText
  446. def set_useMathText(self, val):
  447. r"""
  448. Set whether to use fancy math formatting.
  449. If active, scientific notation is formatted as :math:`1.2 \times 10^3`.
  450. Parameters
  451. ----------
  452. val : bool or None
  453. *None* resets to :rc:`axes.formatter.use_mathtext`.
  454. """
  455. if val is None:
  456. self._useMathText = mpl.rcParams['axes.formatter.use_mathtext']
  457. if self._useMathText is False:
  458. try:
  459. from matplotlib import font_manager
  460. ufont = font_manager.findfont(
  461. font_manager.FontProperties(
  462. family=mpl.rcParams["font.family"]
  463. ),
  464. fallback_to_default=False,
  465. )
  466. except ValueError:
  467. ufont = None
  468. if ufont == str(cbook._get_data_path("fonts/ttf/cmr10.ttf")):
  469. _api.warn_external(
  470. "cmr10 font should ideally be used with "
  471. "mathtext, set axes.formatter.use_mathtext to True"
  472. )
  473. else:
  474. self._useMathText = val
  475. useMathText = property(fget=get_useMathText, fset=set_useMathText)
  476. def __call__(self, x, pos=None):
  477. """
  478. Return the format for tick value *x* at position *pos*.
  479. """
  480. if len(self.locs) == 0:
  481. return ''
  482. else:
  483. xp = (x - self.offset) / (10. ** self.orderOfMagnitude)
  484. if abs(xp) < 1e-8:
  485. xp = 0
  486. return self._format_maybe_minus_and_locale(self.format, xp)
  487. def set_scientific(self, b):
  488. """
  489. Turn scientific notation on or off.
  490. See Also
  491. --------
  492. ScalarFormatter.set_powerlimits
  493. """
  494. self._scientific = bool(b)
  495. def set_powerlimits(self, lims):
  496. r"""
  497. Set size thresholds for scientific notation.
  498. Parameters
  499. ----------
  500. lims : (int, int)
  501. A tuple *(min_exp, max_exp)* containing the powers of 10 that
  502. determine the switchover threshold. For a number representable as
  503. :math:`a \times 10^\mathrm{exp}` with :math:`1 <= |a| < 10`,
  504. scientific notation will be used if ``exp <= min_exp`` or
  505. ``exp >= max_exp``.
  506. The default limits are controlled by :rc:`axes.formatter.limits`.
  507. In particular numbers with *exp* equal to the thresholds are
  508. written in scientific notation.
  509. Typically, *min_exp* will be negative and *max_exp* will be
  510. positive.
  511. For example, ``formatter.set_powerlimits((-3, 4))`` will provide
  512. the following formatting:
  513. :math:`1 \times 10^{-3}, 9.9 \times 10^{-3}, 0.01,`
  514. :math:`9999, 1 \times 10^4`.
  515. See Also
  516. --------
  517. ScalarFormatter.set_scientific
  518. """
  519. if len(lims) != 2:
  520. raise ValueError("'lims' must be a sequence of length 2")
  521. self._powerlimits = lims
  522. def format_data_short(self, value):
  523. # docstring inherited
  524. if value is np.ma.masked:
  525. return ""
  526. if isinstance(value, Integral):
  527. fmt = "%d"
  528. else:
  529. if getattr(self.axis, "__name__", "") in ["xaxis", "yaxis"]:
  530. if self.axis.__name__ == "xaxis":
  531. axis_trf = self.axis.axes.get_xaxis_transform()
  532. axis_inv_trf = axis_trf.inverted()
  533. screen_xy = axis_trf.transform((value, 0))
  534. neighbor_values = axis_inv_trf.transform(
  535. screen_xy + [[-1, 0], [+1, 0]])[:, 0]
  536. else: # yaxis:
  537. axis_trf = self.axis.axes.get_yaxis_transform()
  538. axis_inv_trf = axis_trf.inverted()
  539. screen_xy = axis_trf.transform((0, value))
  540. neighbor_values = axis_inv_trf.transform(
  541. screen_xy + [[0, -1], [0, +1]])[:, 1]
  542. delta = abs(neighbor_values - value).max()
  543. else:
  544. # Rough approximation: no more than 1e4 divisions.
  545. a, b = self.axis.get_view_interval()
  546. delta = (b - a) / 1e4
  547. fmt = f"%-#.{cbook._g_sig_digits(value, delta)}g"
  548. return self._format_maybe_minus_and_locale(fmt, value)
  549. def format_data(self, value):
  550. # docstring inherited
  551. e = math.floor(math.log10(abs(value)))
  552. s = round(value / 10**e, 10)
  553. significand = self._format_maybe_minus_and_locale(
  554. "%d" if s % 1 == 0 else "%1.10g", s)
  555. if e == 0:
  556. return significand
  557. exponent = self._format_maybe_minus_and_locale("%d", e)
  558. if self._useMathText or self._usetex:
  559. exponent = "10^{%s}" % exponent
  560. return (exponent if s == 1 # reformat 1x10^y as 10^y
  561. else rf"{significand} \times {exponent}")
  562. else:
  563. return f"{significand}e{exponent}"
  564. def get_offset(self):
  565. """
  566. Return scientific notation, plus offset.
  567. """
  568. if len(self.locs) == 0:
  569. return ''
  570. if self.orderOfMagnitude or self.offset:
  571. offsetStr = ''
  572. sciNotStr = ''
  573. if self.offset:
  574. offsetStr = self.format_data(self.offset)
  575. if self.offset > 0:
  576. offsetStr = '+' + offsetStr
  577. if self.orderOfMagnitude:
  578. if self._usetex or self._useMathText:
  579. sciNotStr = self.format_data(10 ** self.orderOfMagnitude)
  580. else:
  581. sciNotStr = '1e%d' % self.orderOfMagnitude
  582. if self._useMathText or self._usetex:
  583. if sciNotStr != '':
  584. sciNotStr = r'\times\mathdefault{%s}' % sciNotStr
  585. s = fr'${sciNotStr}\mathdefault{{{offsetStr}}}$'
  586. else:
  587. s = ''.join((sciNotStr, offsetStr))
  588. return self.fix_minus(s)
  589. return ''
  590. def set_locs(self, locs):
  591. # docstring inherited
  592. self.locs = locs
  593. if len(self.locs) > 0:
  594. if self._useOffset:
  595. self._compute_offset()
  596. self._set_order_of_magnitude()
  597. self._set_format()
  598. def _compute_offset(self):
  599. locs = self.locs
  600. # Restrict to visible ticks.
  601. vmin, vmax = sorted(self.axis.get_view_interval())
  602. locs = np.asarray(locs)
  603. locs = locs[(vmin <= locs) & (locs <= vmax)]
  604. if not len(locs):
  605. self.offset = 0
  606. return
  607. lmin, lmax = locs.min(), locs.max()
  608. # Only use offset if there are at least two ticks and every tick has
  609. # the same sign.
  610. if lmin == lmax or lmin <= 0 <= lmax:
  611. self.offset = 0
  612. return
  613. # min, max comparing absolute values (we want division to round towards
  614. # zero so we work on absolute values).
  615. abs_min, abs_max = sorted([abs(float(lmin)), abs(float(lmax))])
  616. sign = math.copysign(1, lmin)
  617. # What is the smallest power of ten such that abs_min and abs_max are
  618. # equal up to that precision?
  619. # Note: Internally using oom instead of 10 ** oom avoids some numerical
  620. # accuracy issues.
  621. oom_max = np.ceil(math.log10(abs_max))
  622. oom = 1 + next(oom for oom in itertools.count(oom_max, -1)
  623. if abs_min // 10 ** oom != abs_max // 10 ** oom)
  624. if (abs_max - abs_min) / 10 ** oom <= 1e-2:
  625. # Handle the case of straddling a multiple of a large power of ten
  626. # (relative to the span).
  627. # What is the smallest power of ten such that abs_min and abs_max
  628. # are no more than 1 apart at that precision?
  629. oom = 1 + next(oom for oom in itertools.count(oom_max, -1)
  630. if abs_max // 10 ** oom - abs_min // 10 ** oom > 1)
  631. # Only use offset if it saves at least _offset_threshold digits.
  632. n = self._offset_threshold - 1
  633. self.offset = (sign * (abs_max // 10 ** oom) * 10 ** oom
  634. if abs_max // 10 ** oom >= 10**n
  635. else 0)
  636. def _set_order_of_magnitude(self):
  637. # if scientific notation is to be used, find the appropriate exponent
  638. # if using a numerical offset, find the exponent after applying the
  639. # offset. When lower power limit = upper <> 0, use provided exponent.
  640. if not self._scientific:
  641. self.orderOfMagnitude = 0
  642. return
  643. if self._powerlimits[0] == self._powerlimits[1] != 0:
  644. # fixed scaling when lower power limit = upper <> 0.
  645. self.orderOfMagnitude = self._powerlimits[0]
  646. return
  647. # restrict to visible ticks
  648. vmin, vmax = sorted(self.axis.get_view_interval())
  649. locs = np.asarray(self.locs)
  650. locs = locs[(vmin <= locs) & (locs <= vmax)]
  651. locs = np.abs(locs)
  652. if not len(locs):
  653. self.orderOfMagnitude = 0
  654. return
  655. if self.offset:
  656. oom = math.floor(math.log10(vmax - vmin))
  657. else:
  658. val = locs.max()
  659. if val == 0:
  660. oom = 0
  661. else:
  662. oom = math.floor(math.log10(val))
  663. if oom <= self._powerlimits[0]:
  664. self.orderOfMagnitude = oom
  665. elif oom >= self._powerlimits[1]:
  666. self.orderOfMagnitude = oom
  667. else:
  668. self.orderOfMagnitude = 0
  669. def _set_format(self):
  670. # set the format string to format all the ticklabels
  671. if len(self.locs) < 2:
  672. # Temporarily augment the locations with the axis end points.
  673. _locs = [*self.locs, *self.axis.get_view_interval()]
  674. else:
  675. _locs = self.locs
  676. locs = (np.asarray(_locs) - self.offset) / 10. ** self.orderOfMagnitude
  677. loc_range = np.ptp(locs)
  678. # Curvilinear coordinates can yield two identical points.
  679. if loc_range == 0:
  680. loc_range = np.max(np.abs(locs))
  681. # Both points might be zero.
  682. if loc_range == 0:
  683. loc_range = 1
  684. if len(self.locs) < 2:
  685. # We needed the end points only for the loc_range calculation.
  686. locs = locs[:-2]
  687. loc_range_oom = int(math.floor(math.log10(loc_range)))
  688. # first estimate:
  689. sigfigs = max(0, 3 - loc_range_oom)
  690. # refined estimate:
  691. thresh = 1e-3 * 10 ** loc_range_oom
  692. while sigfigs >= 0:
  693. if np.abs(locs - np.round(locs, decimals=sigfigs)).max() < thresh:
  694. sigfigs -= 1
  695. else:
  696. break
  697. sigfigs += 1
  698. self.format = f'%1.{sigfigs}f'
  699. if self._usetex or self._useMathText:
  700. self.format = r'$\mathdefault{%s}$' % self.format
  701. class LogFormatter(Formatter):
  702. """
  703. Base class for formatting ticks on a log or symlog scale.
  704. It may be instantiated directly, or subclassed.
  705. Parameters
  706. ----------
  707. base : float, default: 10.
  708. Base of the logarithm used in all calculations.
  709. labelOnlyBase : bool, default: False
  710. If True, label ticks only at integer powers of base.
  711. This is normally True for major ticks and False for
  712. minor ticks.
  713. minor_thresholds : (subset, all), default: (1, 0.4)
  714. If labelOnlyBase is False, these two numbers control
  715. the labeling of ticks that are not at integer powers of
  716. base; normally these are the minor ticks. The controlling
  717. parameter is the log of the axis data range. In the typical
  718. case where base is 10 it is the number of decades spanned
  719. by the axis, so we can call it 'numdec'. If ``numdec <= all``,
  720. all minor ticks will be labeled. If ``all < numdec <= subset``,
  721. then only a subset of minor ticks will be labeled, so as to
  722. avoid crowding. If ``numdec > subset`` then no minor ticks will
  723. be labeled.
  724. linthresh : None or float, default: None
  725. If a symmetric log scale is in use, its ``linthresh``
  726. parameter must be supplied here.
  727. Notes
  728. -----
  729. The `set_locs` method must be called to enable the subsetting
  730. logic controlled by the ``minor_thresholds`` parameter.
  731. In some cases such as the colorbar, there is no distinction between
  732. major and minor ticks; the tick locations might be set manually,
  733. or by a locator that puts ticks at integer powers of base and
  734. at intermediate locations. For this situation, disable the
  735. minor_thresholds logic by using ``minor_thresholds=(np.inf, np.inf)``,
  736. so that all ticks will be labeled.
  737. To disable labeling of minor ticks when 'labelOnlyBase' is False,
  738. use ``minor_thresholds=(0, 0)``. This is the default for the
  739. "classic" style.
  740. Examples
  741. --------
  742. To label a subset of minor ticks when the view limits span up
  743. to 2 decades, and all of the ticks when zoomed in to 0.5 decades
  744. or less, use ``minor_thresholds=(2, 0.5)``.
  745. To label all minor ticks when the view limits span up to 1.5
  746. decades, use ``minor_thresholds=(1.5, 1.5)``.
  747. """
  748. def __init__(self, base=10.0, labelOnlyBase=False,
  749. minor_thresholds=None,
  750. linthresh=None):
  751. self.set_base(base)
  752. self.set_label_minor(labelOnlyBase)
  753. if minor_thresholds is None:
  754. if mpl.rcParams['_internal.classic_mode']:
  755. minor_thresholds = (0, 0)
  756. else:
  757. minor_thresholds = (1, 0.4)
  758. self.minor_thresholds = minor_thresholds
  759. self._sublabels = None
  760. self._linthresh = linthresh
  761. def set_base(self, base):
  762. """
  763. Change the *base* for labeling.
  764. .. warning::
  765. Should always match the base used for :class:`LogLocator`
  766. """
  767. self._base = float(base)
  768. def set_label_minor(self, labelOnlyBase):
  769. """
  770. Switch minor tick labeling on or off.
  771. Parameters
  772. ----------
  773. labelOnlyBase : bool
  774. If True, label ticks only at integer powers of base.
  775. """
  776. self.labelOnlyBase = labelOnlyBase
  777. def set_locs(self, locs=None):
  778. """
  779. Use axis view limits to control which ticks are labeled.
  780. The *locs* parameter is ignored in the present algorithm.
  781. """
  782. if np.isinf(self.minor_thresholds[0]):
  783. self._sublabels = None
  784. return
  785. # Handle symlog case:
  786. linthresh = self._linthresh
  787. if linthresh is None:
  788. try:
  789. linthresh = self.axis.get_transform().linthresh
  790. except AttributeError:
  791. pass
  792. vmin, vmax = self.axis.get_view_interval()
  793. if vmin > vmax:
  794. vmin, vmax = vmax, vmin
  795. if linthresh is None and vmin <= 0:
  796. # It's probably a colorbar with
  797. # a format kwarg setting a LogFormatter in the manner
  798. # that worked with 1.5.x, but that doesn't work now.
  799. self._sublabels = {1} # label powers of base
  800. return
  801. b = self._base
  802. if linthresh is not None: # symlog
  803. # Only compute the number of decades in the logarithmic part of the
  804. # axis
  805. numdec = 0
  806. if vmin < -linthresh:
  807. rhs = min(vmax, -linthresh)
  808. numdec += math.log(vmin / rhs) / math.log(b)
  809. if vmax > linthresh:
  810. lhs = max(vmin, linthresh)
  811. numdec += math.log(vmax / lhs) / math.log(b)
  812. else:
  813. vmin = math.log(vmin) / math.log(b)
  814. vmax = math.log(vmax) / math.log(b)
  815. numdec = abs(vmax - vmin)
  816. if numdec > self.minor_thresholds[0]:
  817. # Label only bases
  818. self._sublabels = {1}
  819. elif numdec > self.minor_thresholds[1]:
  820. # Add labels between bases at log-spaced coefficients;
  821. # include base powers in case the locations include
  822. # "major" and "minor" points, as in colorbar.
  823. c = np.geomspace(1, b, int(b)//2 + 1)
  824. self._sublabels = set(np.round(c))
  825. # For base 10, this yields (1, 2, 3, 4, 6, 10).
  826. else:
  827. # Label all integer multiples of base**n.
  828. self._sublabels = set(np.arange(1, b + 1))
  829. def _num_to_string(self, x, vmin, vmax):
  830. return self._pprint_val(x, vmax - vmin) if 1 <= x <= 10000 else f"{x:1.0e}"
  831. def __call__(self, x, pos=None):
  832. # docstring inherited
  833. if x == 0.0: # Symlog
  834. return '0'
  835. x = abs(x)
  836. b = self._base
  837. # only label the decades
  838. fx = math.log(x) / math.log(b)
  839. is_x_decade = _is_close_to_int(fx)
  840. exponent = round(fx) if is_x_decade else np.floor(fx)
  841. coeff = round(b ** (fx - exponent))
  842. if self.labelOnlyBase and not is_x_decade:
  843. return ''
  844. if self._sublabels is not None and coeff not in self._sublabels:
  845. return ''
  846. vmin, vmax = self.axis.get_view_interval()
  847. vmin, vmax = mtransforms.nonsingular(vmin, vmax, expander=0.05)
  848. s = self._num_to_string(x, vmin, vmax)
  849. return self.fix_minus(s)
  850. def format_data(self, value):
  851. with cbook._setattr_cm(self, labelOnlyBase=False):
  852. return cbook.strip_math(self.__call__(value))
  853. def format_data_short(self, value):
  854. # docstring inherited
  855. return ('%-12g' % value).rstrip()
  856. def _pprint_val(self, x, d):
  857. # If the number is not too big and it's an int, format it as an int.
  858. if abs(x) < 1e4 and x == int(x):
  859. return '%d' % x
  860. fmt = ('%1.3e' if d < 1e-2 else
  861. '%1.3f' if d <= 1 else
  862. '%1.2f' if d <= 10 else
  863. '%1.1f' if d <= 1e5 else
  864. '%1.1e')
  865. s = fmt % x
  866. tup = s.split('e')
  867. if len(tup) == 2:
  868. mantissa = tup[0].rstrip('0').rstrip('.')
  869. exponent = int(tup[1])
  870. if exponent:
  871. s = '%se%d' % (mantissa, exponent)
  872. else:
  873. s = mantissa
  874. else:
  875. s = s.rstrip('0').rstrip('.')
  876. return s
  877. class LogFormatterExponent(LogFormatter):
  878. """
  879. Format values for log axis using ``exponent = log_base(value)``.
  880. """
  881. def _num_to_string(self, x, vmin, vmax):
  882. fx = math.log(x) / math.log(self._base)
  883. if 1 <= abs(fx) <= 10000:
  884. fd = math.log(vmax - vmin) / math.log(self._base)
  885. s = self._pprint_val(fx, fd)
  886. else:
  887. s = f"{fx:1.0g}"
  888. return s
  889. class LogFormatterMathtext(LogFormatter):
  890. """
  891. Format values for log axis using ``exponent = log_base(value)``.
  892. """
  893. def _non_decade_format(self, sign_string, base, fx, usetex):
  894. """Return string for non-decade locations."""
  895. return r'$\mathdefault{%s%s^{%.2f}}$' % (sign_string, base, fx)
  896. def __call__(self, x, pos=None):
  897. # docstring inherited
  898. if x == 0: # Symlog
  899. return r'$\mathdefault{0}$'
  900. sign_string = '-' if x < 0 else ''
  901. x = abs(x)
  902. b = self._base
  903. # only label the decades
  904. fx = math.log(x) / math.log(b)
  905. is_x_decade = _is_close_to_int(fx)
  906. exponent = round(fx) if is_x_decade else np.floor(fx)
  907. coeff = round(b ** (fx - exponent))
  908. if self.labelOnlyBase and not is_x_decade:
  909. return ''
  910. if self._sublabels is not None and coeff not in self._sublabels:
  911. return ''
  912. if is_x_decade:
  913. fx = round(fx)
  914. # use string formatting of the base if it is not an integer
  915. if b % 1 == 0.0:
  916. base = '%d' % b
  917. else:
  918. base = '%s' % b
  919. if abs(fx) < mpl.rcParams['axes.formatter.min_exponent']:
  920. return r'$\mathdefault{%s%g}$' % (sign_string, x)
  921. elif not is_x_decade:
  922. usetex = mpl.rcParams['text.usetex']
  923. return self._non_decade_format(sign_string, base, fx, usetex)
  924. else:
  925. return r'$\mathdefault{%s%s^{%d}}$' % (sign_string, base, fx)
  926. class LogFormatterSciNotation(LogFormatterMathtext):
  927. """
  928. Format values following scientific notation in a logarithmic axis.
  929. """
  930. def _non_decade_format(self, sign_string, base, fx, usetex):
  931. """Return string for non-decade locations."""
  932. b = float(base)
  933. exponent = math.floor(fx)
  934. coeff = b ** (fx - exponent)
  935. if _is_close_to_int(coeff):
  936. coeff = round(coeff)
  937. return r'$\mathdefault{%s%g\times%s^{%d}}$' \
  938. % (sign_string, coeff, base, exponent)
  939. class LogitFormatter(Formatter):
  940. """
  941. Probability formatter (using Math text).
  942. """
  943. def __init__(
  944. self,
  945. *,
  946. use_overline=False,
  947. one_half=r"\frac{1}{2}",
  948. minor=False,
  949. minor_threshold=25,
  950. minor_number=6,
  951. ):
  952. r"""
  953. Parameters
  954. ----------
  955. use_overline : bool, default: False
  956. If x > 1/2, with x = 1 - v, indicate if x should be displayed as
  957. $\overline{v}$. The default is to display $1 - v$.
  958. one_half : str, default: r"\\frac{1}{2}"
  959. The string used to represent 1/2.
  960. minor : bool, default: False
  961. Indicate if the formatter is formatting minor ticks or not.
  962. Basically minor ticks are not labelled, except when only few ticks
  963. are provided, ticks with most space with neighbor ticks are
  964. labelled. See other parameters to change the default behavior.
  965. minor_threshold : int, default: 25
  966. Maximum number of locs for labelling some minor ticks. This
  967. parameter have no effect if minor is False.
  968. minor_number : int, default: 6
  969. Number of ticks which are labelled when the number of ticks is
  970. below the threshold.
  971. """
  972. self._use_overline = use_overline
  973. self._one_half = one_half
  974. self._minor = minor
  975. self._labelled = set()
  976. self._minor_threshold = minor_threshold
  977. self._minor_number = minor_number
  978. def use_overline(self, use_overline):
  979. r"""
  980. Switch display mode with overline for labelling p>1/2.
  981. Parameters
  982. ----------
  983. use_overline : bool
  984. If x > 1/2, with x = 1 - v, indicate if x should be displayed as
  985. $\overline{v}$. The default is to display $1 - v$.
  986. """
  987. self._use_overline = use_overline
  988. def set_one_half(self, one_half):
  989. r"""
  990. Set the way one half is displayed.
  991. one_half : str
  992. The string used to represent 1/2.
  993. """
  994. self._one_half = one_half
  995. def set_minor_threshold(self, minor_threshold):
  996. """
  997. Set the threshold for labelling minors ticks.
  998. Parameters
  999. ----------
  1000. minor_threshold : int
  1001. Maximum number of locations for labelling some minor ticks. This
  1002. parameter have no effect if minor is False.
  1003. """
  1004. self._minor_threshold = minor_threshold
  1005. def set_minor_number(self, minor_number):
  1006. """
  1007. Set the number of minor ticks to label when some minor ticks are
  1008. labelled.
  1009. Parameters
  1010. ----------
  1011. minor_number : int
  1012. Number of ticks which are labelled when the number of ticks is
  1013. below the threshold.
  1014. """
  1015. self._minor_number = minor_number
  1016. def set_locs(self, locs):
  1017. self.locs = np.array(locs)
  1018. self._labelled.clear()
  1019. if not self._minor:
  1020. return None
  1021. if all(
  1022. _is_decade(x, rtol=1e-7)
  1023. or _is_decade(1 - x, rtol=1e-7)
  1024. or (_is_close_to_int(2 * x) and
  1025. int(np.round(2 * x)) == 1)
  1026. for x in locs
  1027. ):
  1028. # minor ticks are subsample from ideal, so no label
  1029. return None
  1030. if len(locs) < self._minor_threshold:
  1031. if len(locs) < self._minor_number:
  1032. self._labelled.update(locs)
  1033. else:
  1034. # we do not have a lot of minor ticks, so only few decades are
  1035. # displayed, then we choose some (spaced) minor ticks to label.
  1036. # Only minor ticks are known, we assume it is sufficient to
  1037. # choice which ticks are displayed.
  1038. # For each ticks we compute the distance between the ticks and
  1039. # the previous, and between the ticks and the next one. Ticks
  1040. # with smallest minimum are chosen. As tiebreak, the ticks
  1041. # with smallest sum is chosen.
  1042. diff = np.diff(-np.log(1 / self.locs - 1))
  1043. space_pessimistic = np.minimum(
  1044. np.concatenate(((np.inf,), diff)),
  1045. np.concatenate((diff, (np.inf,))),
  1046. )
  1047. space_sum = (
  1048. np.concatenate(((0,), diff))
  1049. + np.concatenate((diff, (0,)))
  1050. )
  1051. good_minor = sorted(
  1052. range(len(self.locs)),
  1053. key=lambda i: (space_pessimistic[i], space_sum[i]),
  1054. )[-self._minor_number:]
  1055. self._labelled.update(locs[i] for i in good_minor)
  1056. def _format_value(self, x, locs, sci_notation=True):
  1057. if sci_notation:
  1058. exponent = math.floor(np.log10(x))
  1059. min_precision = 0
  1060. else:
  1061. exponent = 0
  1062. min_precision = 1
  1063. value = x * 10 ** (-exponent)
  1064. if len(locs) < 2:
  1065. precision = min_precision
  1066. else:
  1067. diff = np.sort(np.abs(locs - x))[1]
  1068. precision = -np.log10(diff) + exponent
  1069. precision = (
  1070. int(np.round(precision))
  1071. if _is_close_to_int(precision)
  1072. else math.ceil(precision)
  1073. )
  1074. if precision < min_precision:
  1075. precision = min_precision
  1076. mantissa = r"%.*f" % (precision, value)
  1077. if not sci_notation:
  1078. return mantissa
  1079. s = r"%s\cdot10^{%d}" % (mantissa, exponent)
  1080. return s
  1081. def _one_minus(self, s):
  1082. if self._use_overline:
  1083. return r"\overline{%s}" % s
  1084. else:
  1085. return f"1-{s}"
  1086. def __call__(self, x, pos=None):
  1087. if self._minor and x not in self._labelled:
  1088. return ""
  1089. if x <= 0 or x >= 1:
  1090. return ""
  1091. if _is_close_to_int(2 * x) and round(2 * x) == 1:
  1092. s = self._one_half
  1093. elif x < 0.5 and _is_decade(x, rtol=1e-7):
  1094. exponent = round(math.log10(x))
  1095. s = "10^{%d}" % exponent
  1096. elif x > 0.5 and _is_decade(1 - x, rtol=1e-7):
  1097. exponent = round(math.log10(1 - x))
  1098. s = self._one_minus("10^{%d}" % exponent)
  1099. elif x < 0.1:
  1100. s = self._format_value(x, self.locs)
  1101. elif x > 0.9:
  1102. s = self._one_minus(self._format_value(1-x, 1-self.locs))
  1103. else:
  1104. s = self._format_value(x, self.locs, sci_notation=False)
  1105. return r"$\mathdefault{%s}$" % s
  1106. def format_data_short(self, value):
  1107. # docstring inherited
  1108. # Thresholds chosen to use scientific notation iff exponent <= -2.
  1109. if value < 0.1:
  1110. return f"{value:e}"
  1111. if value < 0.9:
  1112. return f"{value:f}"
  1113. return f"1-{1 - value:e}"
  1114. class EngFormatter(ScalarFormatter):
  1115. """
  1116. Format axis values using engineering prefixes to represent powers
  1117. of 1000, plus a specified unit, e.g., 10 MHz instead of 1e7.
  1118. """
  1119. # The SI engineering prefixes
  1120. ENG_PREFIXES = {
  1121. -30: "q",
  1122. -27: "r",
  1123. -24: "y",
  1124. -21: "z",
  1125. -18: "a",
  1126. -15: "f",
  1127. -12: "p",
  1128. -9: "n",
  1129. -6: "\N{MICRO SIGN}",
  1130. -3: "m",
  1131. 0: "",
  1132. 3: "k",
  1133. 6: "M",
  1134. 9: "G",
  1135. 12: "T",
  1136. 15: "P",
  1137. 18: "E",
  1138. 21: "Z",
  1139. 24: "Y",
  1140. 27: "R",
  1141. 30: "Q"
  1142. }
  1143. def __init__(self, unit="", places=None, sep=" ", *, usetex=None,
  1144. useMathText=None, useOffset=False):
  1145. r"""
  1146. Parameters
  1147. ----------
  1148. unit : str, default: ""
  1149. Unit symbol to use, suitable for use with single-letter
  1150. representations of powers of 1000. For example, 'Hz' or 'm'.
  1151. places : int, default: None
  1152. Precision with which to display the number, specified in
  1153. digits after the decimal point (there will be between one
  1154. and three digits before the decimal point). If it is None,
  1155. the formatting falls back to the floating point format '%g',
  1156. which displays up to 6 *significant* digits, i.e. the equivalent
  1157. value for *places* varies between 0 and 5 (inclusive).
  1158. sep : str, default: " "
  1159. Separator used between the value and the prefix/unit. For
  1160. example, one get '3.14 mV' if ``sep`` is " " (default) and
  1161. '3.14mV' if ``sep`` is "". Besides the default behavior, some
  1162. other useful options may be:
  1163. * ``sep=""`` to append directly the prefix/unit to the value;
  1164. * ``sep="\N{THIN SPACE}"`` (``U+2009``);
  1165. * ``sep="\N{NARROW NO-BREAK SPACE}"`` (``U+202F``);
  1166. * ``sep="\N{NO-BREAK SPACE}"`` (``U+00A0``).
  1167. usetex : bool, default: :rc:`text.usetex`
  1168. To enable/disable the use of TeX's math mode for rendering the
  1169. numbers in the formatter.
  1170. useMathText : bool, default: :rc:`axes.formatter.use_mathtext`
  1171. To enable/disable the use mathtext for rendering the numbers in
  1172. the formatter.
  1173. useOffset : bool or float, default: False
  1174. Whether to use offset notation with :math:`10^{3*N}` based prefixes.
  1175. This features allows showing an offset with standard SI order of
  1176. magnitude prefix near the axis. Offset is computed similarly to
  1177. how `ScalarFormatter` computes it internally, but here you are
  1178. guaranteed to get an offset which will make the tick labels exceed
  1179. 3 digits. See also `.set_useOffset`.
  1180. .. versionadded:: 3.10
  1181. """
  1182. self.unit = unit
  1183. self.places = places
  1184. self.sep = sep
  1185. super().__init__(
  1186. useOffset=useOffset,
  1187. useMathText=useMathText,
  1188. useLocale=False,
  1189. usetex=usetex,
  1190. )
  1191. def __call__(self, x, pos=None):
  1192. """
  1193. Return the format for tick value *x* at position *pos*.
  1194. If there is no currently offset in the data, it returns the best
  1195. engineering formatting that fits the given argument, independently.
  1196. """
  1197. if len(self.locs) == 0 or self.offset == 0:
  1198. return self.fix_minus(self.format_data(x))
  1199. else:
  1200. xp = (x - self.offset) / (10. ** self.orderOfMagnitude)
  1201. if abs(xp) < 1e-8:
  1202. xp = 0
  1203. return self._format_maybe_minus_and_locale(self.format, xp)
  1204. def set_locs(self, locs):
  1205. # docstring inherited
  1206. self.locs = locs
  1207. if len(self.locs) > 0:
  1208. vmin, vmax = sorted(self.axis.get_view_interval())
  1209. if self._useOffset:
  1210. self._compute_offset()
  1211. if self.offset != 0:
  1212. # We don't want to use the offset computed by
  1213. # self._compute_offset because it rounds the offset unaware
  1214. # of our engineering prefixes preference, and this can
  1215. # cause ticks with 4+ digits to appear. These ticks are
  1216. # slightly less readable, so if offset is justified
  1217. # (decided by self._compute_offset) we set it to better
  1218. # value:
  1219. self.offset = round((vmin + vmax)/2, 3)
  1220. # Use log1000 to use engineers' oom standards
  1221. self.orderOfMagnitude = math.floor(math.log(vmax - vmin, 1000))*3
  1222. self._set_format()
  1223. # Simplify a bit ScalarFormatter.get_offset: We always want to use
  1224. # self.format_data. Also we want to return a non-empty string only if there
  1225. # is an offset, no matter what is self.orderOfMagnitude. If there _is_ an
  1226. # offset, self.orderOfMagnitude is consulted. This behavior is verified
  1227. # in `test_ticker.py`.
  1228. def get_offset(self):
  1229. # docstring inherited
  1230. if len(self.locs) == 0:
  1231. return ''
  1232. if self.offset:
  1233. offsetStr = ''
  1234. if self.offset:
  1235. offsetStr = self.format_data(self.offset)
  1236. if self.offset > 0:
  1237. offsetStr = '+' + offsetStr
  1238. sciNotStr = self.format_data(10 ** self.orderOfMagnitude)
  1239. if self._useMathText or self._usetex:
  1240. if sciNotStr != '':
  1241. sciNotStr = r'\times%s' % sciNotStr
  1242. s = f'${sciNotStr}{offsetStr}$'
  1243. else:
  1244. s = sciNotStr + offsetStr
  1245. return self.fix_minus(s)
  1246. return ''
  1247. def format_eng(self, num):
  1248. """Alias to EngFormatter.format_data"""
  1249. return self.format_data(num)
  1250. def format_data(self, value):
  1251. """
  1252. Format a number in engineering notation, appending a letter
  1253. representing the power of 1000 of the original number.
  1254. Some examples:
  1255. >>> format_data(0) # for self.places = 0
  1256. '0'
  1257. >>> format_data(1000000) # for self.places = 1
  1258. '1.0 M'
  1259. >>> format_data(-1e-6) # for self.places = 2
  1260. '-1.00 \N{MICRO SIGN}'
  1261. """
  1262. sign = 1
  1263. fmt = "g" if self.places is None else f".{self.places:d}f"
  1264. if value < 0:
  1265. sign = -1
  1266. value = -value
  1267. if value != 0:
  1268. pow10 = int(math.floor(math.log10(value) / 3) * 3)
  1269. else:
  1270. pow10 = 0
  1271. # Force value to zero, to avoid inconsistencies like
  1272. # format_eng(-0) = "0" and format_eng(0.0) = "0"
  1273. # but format_eng(-0.0) = "-0.0"
  1274. value = 0.0
  1275. pow10 = np.clip(pow10, min(self.ENG_PREFIXES), max(self.ENG_PREFIXES))
  1276. mant = sign * value / (10.0 ** pow10)
  1277. # Taking care of the cases like 999.9..., which may be rounded to 1000
  1278. # instead of 1 k. Beware of the corner case of values that are beyond
  1279. # the range of SI prefixes (i.e. > 'Y').
  1280. if (abs(float(format(mant, fmt))) >= 1000
  1281. and pow10 < max(self.ENG_PREFIXES)):
  1282. mant /= 1000
  1283. pow10 += 3
  1284. unit_prefix = self.ENG_PREFIXES[int(pow10)]
  1285. if self.unit or unit_prefix:
  1286. suffix = f"{self.sep}{unit_prefix}{self.unit}"
  1287. else:
  1288. suffix = ""
  1289. if self._usetex or self._useMathText:
  1290. return f"${mant:{fmt}}${suffix}"
  1291. else:
  1292. return f"{mant:{fmt}}{suffix}"
  1293. class PercentFormatter(Formatter):
  1294. """
  1295. Format numbers as a percentage.
  1296. Parameters
  1297. ----------
  1298. xmax : float
  1299. Determines how the number is converted into a percentage.
  1300. *xmax* is the data value that corresponds to 100%.
  1301. Percentages are computed as ``x / xmax * 100``. So if the data is
  1302. already scaled to be percentages, *xmax* will be 100. Another common
  1303. situation is where *xmax* is 1.0.
  1304. decimals : None or int
  1305. The number of decimal places to place after the point.
  1306. If *None* (the default), the number will be computed automatically.
  1307. symbol : str or None
  1308. A string that will be appended to the label. It may be
  1309. *None* or empty to indicate that no symbol should be used. LaTeX
  1310. special characters are escaped in *symbol* whenever latex mode is
  1311. enabled, unless *is_latex* is *True*.
  1312. is_latex : bool
  1313. If *False*, reserved LaTeX characters in *symbol* will be escaped.
  1314. """
  1315. def __init__(self, xmax=100, decimals=None, symbol='%', is_latex=False):
  1316. self.xmax = xmax + 0.0
  1317. self.decimals = decimals
  1318. self._symbol = symbol
  1319. self._is_latex = is_latex
  1320. def __call__(self, x, pos=None):
  1321. """Format the tick as a percentage with the appropriate scaling."""
  1322. ax_min, ax_max = self.axis.get_view_interval()
  1323. display_range = abs(ax_max - ax_min)
  1324. return self.fix_minus(self.format_pct(x, display_range))
  1325. def format_pct(self, x, display_range):
  1326. """
  1327. Format the number as a percentage number with the correct
  1328. number of decimals and adds the percent symbol, if any.
  1329. If ``self.decimals`` is `None`, the number of digits after the
  1330. decimal point is set based on the *display_range* of the axis
  1331. as follows:
  1332. ============= ======== =======================
  1333. display_range decimals sample
  1334. ============= ======== =======================
  1335. >50 0 ``x = 34.5`` => 35%
  1336. >5 1 ``x = 34.5`` => 34.5%
  1337. >0.5 2 ``x = 34.5`` => 34.50%
  1338. ... ... ...
  1339. ============= ======== =======================
  1340. This method will not be very good for tiny axis ranges or
  1341. extremely large ones. It assumes that the values on the chart
  1342. are percentages displayed on a reasonable scale.
  1343. """
  1344. x = self.convert_to_pct(x)
  1345. if self.decimals is None:
  1346. # conversion works because display_range is a difference
  1347. scaled_range = self.convert_to_pct(display_range)
  1348. if scaled_range <= 0:
  1349. decimals = 0
  1350. else:
  1351. # Luckily Python's built-in ceil rounds to +inf, not away from
  1352. # zero. This is very important since the equation for decimals
  1353. # starts out as `scaled_range > 0.5 * 10**(2 - decimals)`
  1354. # and ends up with `decimals > 2 - log10(2 * scaled_range)`.
  1355. decimals = math.ceil(2.0 - math.log10(2.0 * scaled_range))
  1356. if decimals > 5:
  1357. decimals = 5
  1358. elif decimals < 0:
  1359. decimals = 0
  1360. else:
  1361. decimals = self.decimals
  1362. s = f'{x:0.{int(decimals)}f}'
  1363. return s + self.symbol
  1364. def convert_to_pct(self, x):
  1365. return 100.0 * (x / self.xmax)
  1366. @property
  1367. def symbol(self):
  1368. r"""
  1369. The configured percent symbol as a string.
  1370. If LaTeX is enabled via :rc:`text.usetex`, the special characters
  1371. ``{'#', '$', '%', '&', '~', '_', '^', '\', '{', '}'}`` are
  1372. automatically escaped in the string.
  1373. """
  1374. symbol = self._symbol
  1375. if not symbol:
  1376. symbol = ''
  1377. elif not self._is_latex and mpl.rcParams['text.usetex']:
  1378. # Source: http://www.personal.ceu.hu/tex/specchar.htm
  1379. # Backslash must be first for this to work correctly since
  1380. # it keeps getting added in
  1381. for spec in r'\#$%&~_^{}':
  1382. symbol = symbol.replace(spec, '\\' + spec)
  1383. return symbol
  1384. @symbol.setter
  1385. def symbol(self, symbol):
  1386. self._symbol = symbol
  1387. class Locator(TickHelper):
  1388. """
  1389. Determine tick locations.
  1390. Note that the same locator should not be used across multiple
  1391. `~matplotlib.axis.Axis` because the locator stores references to the Axis
  1392. data and view limits.
  1393. """
  1394. # Some automatic tick locators can generate so many ticks they
  1395. # kill the machine when you try and render them.
  1396. # This parameter is set to cause locators to raise an error if too
  1397. # many ticks are generated.
  1398. MAXTICKS = 1000
  1399. def tick_values(self, vmin, vmax):
  1400. """
  1401. Return the values of the located ticks given **vmin** and **vmax**.
  1402. .. note::
  1403. To get tick locations with the vmin and vmax values defined
  1404. automatically for the associated ``axis`` simply call
  1405. the Locator instance::
  1406. >>> print(type(loc))
  1407. <type 'Locator'>
  1408. >>> print(loc())
  1409. [1, 2, 3, 4]
  1410. """
  1411. raise NotImplementedError('Derived must override')
  1412. def set_params(self, **kwargs):
  1413. """
  1414. Do nothing, and raise a warning. Any locator class not supporting the
  1415. set_params() function will call this.
  1416. """
  1417. _api.warn_external(
  1418. "'set_params()' not defined for locator of type " +
  1419. str(type(self)))
  1420. def __call__(self):
  1421. """Return the locations of the ticks."""
  1422. # note: some locators return data limits, other return view limits,
  1423. # hence there is no *one* interface to call self.tick_values.
  1424. raise NotImplementedError('Derived must override')
  1425. def raise_if_exceeds(self, locs):
  1426. """
  1427. Log at WARNING level if *locs* is longer than `Locator.MAXTICKS`.
  1428. This is intended to be called immediately before returning *locs* from
  1429. ``__call__`` to inform users in case their Locator returns a huge
  1430. number of ticks, causing Matplotlib to run out of memory.
  1431. The "strange" name of this method dates back to when it would raise an
  1432. exception instead of emitting a log.
  1433. """
  1434. if len(locs) >= self.MAXTICKS:
  1435. _log.warning(
  1436. "Locator attempting to generate %s ticks ([%s, ..., %s]), "
  1437. "which exceeds Locator.MAXTICKS (%s).",
  1438. len(locs), locs[0], locs[-1], self.MAXTICKS)
  1439. return locs
  1440. def nonsingular(self, v0, v1):
  1441. """
  1442. Adjust a range as needed to avoid singularities.
  1443. This method gets called during autoscaling, with ``(v0, v1)`` set to
  1444. the data limits on the Axes if the Axes contains any data, or
  1445. ``(-inf, +inf)`` if not.
  1446. - If ``v0 == v1`` (possibly up to some floating point slop), this
  1447. method returns an expanded interval around this value.
  1448. - If ``(v0, v1) == (-inf, +inf)``, this method returns appropriate
  1449. default view limits.
  1450. - Otherwise, ``(v0, v1)`` is returned without modification.
  1451. """
  1452. return mtransforms.nonsingular(v0, v1, expander=.05)
  1453. def view_limits(self, vmin, vmax):
  1454. """
  1455. Select a scale for the range from vmin to vmax.
  1456. Subclasses should override this method to change locator behaviour.
  1457. """
  1458. return mtransforms.nonsingular(vmin, vmax)
  1459. class IndexLocator(Locator):
  1460. """
  1461. Place ticks at every nth point plotted.
  1462. IndexLocator assumes index plotting; i.e., that the ticks are placed at integer
  1463. values in the range between 0 and len(data) inclusive.
  1464. """
  1465. def __init__(self, base, offset):
  1466. """Place ticks every *base* data point, starting at *offset*."""
  1467. self._base = base
  1468. self.offset = offset
  1469. def set_params(self, base=None, offset=None):
  1470. """Set parameters within this locator"""
  1471. if base is not None:
  1472. self._base = base
  1473. if offset is not None:
  1474. self.offset = offset
  1475. def __call__(self):
  1476. """Return the locations of the ticks"""
  1477. dmin, dmax = self.axis.get_data_interval()
  1478. return self.tick_values(dmin, dmax)
  1479. def tick_values(self, vmin, vmax):
  1480. return self.raise_if_exceeds(
  1481. np.arange(vmin + self.offset, vmax + 1, self._base))
  1482. class FixedLocator(Locator):
  1483. r"""
  1484. Place ticks at a set of fixed values.
  1485. If *nbins* is None ticks are placed at all values. Otherwise, the *locs* array of
  1486. possible positions will be subsampled to keep the number of ticks
  1487. :math:`\leq nbins + 1`. The subsampling will be done to include the smallest
  1488. absolute value; for example, if zero is included in the array of possibilities, then
  1489. it will be included in the chosen ticks.
  1490. """
  1491. def __init__(self, locs, nbins=None):
  1492. self.locs = np.asarray(locs)
  1493. _api.check_shape((None,), locs=self.locs)
  1494. self.nbins = max(nbins, 2) if nbins is not None else None
  1495. def set_params(self, nbins=None):
  1496. """Set parameters within this locator."""
  1497. if nbins is not None:
  1498. self.nbins = nbins
  1499. def __call__(self):
  1500. return self.tick_values(None, None)
  1501. def tick_values(self, vmin, vmax):
  1502. """
  1503. Return the locations of the ticks.
  1504. .. note::
  1505. Because the values are fixed, vmin and vmax are not used in this
  1506. method.
  1507. """
  1508. if self.nbins is None:
  1509. return self.locs
  1510. step = max(int(np.ceil(len(self.locs) / self.nbins)), 1)
  1511. ticks = self.locs[::step]
  1512. for i in range(1, step):
  1513. ticks1 = self.locs[i::step]
  1514. if np.abs(ticks1).min() < np.abs(ticks).min():
  1515. ticks = ticks1
  1516. return self.raise_if_exceeds(ticks)
  1517. class NullLocator(Locator):
  1518. """
  1519. No ticks
  1520. """
  1521. def __call__(self):
  1522. return self.tick_values(None, None)
  1523. def tick_values(self, vmin, vmax):
  1524. """
  1525. Return the locations of the ticks.
  1526. .. note::
  1527. Because the values are Null, vmin and vmax are not used in this
  1528. method.
  1529. """
  1530. return []
  1531. class LinearLocator(Locator):
  1532. """
  1533. Place ticks at evenly spaced values.
  1534. The first time this function is called it will try to set the
  1535. number of ticks to make a nice tick partitioning. Thereafter, the
  1536. number of ticks will be fixed so that interactive navigation will
  1537. be nice
  1538. """
  1539. def __init__(self, numticks=None, presets=None):
  1540. """
  1541. Parameters
  1542. ----------
  1543. numticks : int or None, default None
  1544. Number of ticks. If None, *numticks* = 11.
  1545. presets : dict or None, default: None
  1546. Dictionary mapping ``(vmin, vmax)`` to an array of locations.
  1547. Overrides *numticks* if there is an entry for the current
  1548. ``(vmin, vmax)``.
  1549. """
  1550. self.numticks = numticks
  1551. if presets is None:
  1552. self.presets = {}
  1553. else:
  1554. self.presets = presets
  1555. @property
  1556. def numticks(self):
  1557. # Old hard-coded default.
  1558. return self._numticks if self._numticks is not None else 11
  1559. @numticks.setter
  1560. def numticks(self, numticks):
  1561. self._numticks = numticks
  1562. def set_params(self, numticks=None, presets=None):
  1563. """Set parameters within this locator."""
  1564. if presets is not None:
  1565. self.presets = presets
  1566. if numticks is not None:
  1567. self.numticks = numticks
  1568. def __call__(self):
  1569. """Return the locations of the ticks."""
  1570. vmin, vmax = self.axis.get_view_interval()
  1571. return self.tick_values(vmin, vmax)
  1572. def tick_values(self, vmin, vmax):
  1573. vmin, vmax = mtransforms.nonsingular(vmin, vmax, expander=0.05)
  1574. if (vmin, vmax) in self.presets:
  1575. return self.presets[(vmin, vmax)]
  1576. if self.numticks == 0:
  1577. return []
  1578. ticklocs = np.linspace(vmin, vmax, self.numticks)
  1579. return self.raise_if_exceeds(ticklocs)
  1580. def view_limits(self, vmin, vmax):
  1581. """Try to choose the view limits intelligently."""
  1582. if vmax < vmin:
  1583. vmin, vmax = vmax, vmin
  1584. if vmin == vmax:
  1585. vmin -= 1
  1586. vmax += 1
  1587. if mpl.rcParams['axes.autolimit_mode'] == 'round_numbers':
  1588. exponent, remainder = divmod(
  1589. math.log10(vmax - vmin), math.log10(max(self.numticks - 1, 1)))
  1590. exponent -= (remainder < .5)
  1591. scale = max(self.numticks - 1, 1) ** (-exponent)
  1592. vmin = math.floor(scale * vmin) / scale
  1593. vmax = math.ceil(scale * vmax) / scale
  1594. return mtransforms.nonsingular(vmin, vmax)
  1595. class MultipleLocator(Locator):
  1596. """
  1597. Place ticks at every integer multiple of a base plus an offset.
  1598. """
  1599. def __init__(self, base=1.0, offset=0.0):
  1600. """
  1601. Parameters
  1602. ----------
  1603. base : float > 0, default: 1.0
  1604. Interval between ticks.
  1605. offset : float, default: 0.0
  1606. Value added to each multiple of *base*.
  1607. .. versionadded:: 3.8
  1608. """
  1609. self._edge = _Edge_integer(base, 0)
  1610. self._offset = offset
  1611. def set_params(self, base=None, offset=None):
  1612. """
  1613. Set parameters within this locator.
  1614. Parameters
  1615. ----------
  1616. base : float > 0, optional
  1617. Interval between ticks.
  1618. offset : float, optional
  1619. Value added to each multiple of *base*.
  1620. .. versionadded:: 3.8
  1621. """
  1622. if base is not None:
  1623. self._edge = _Edge_integer(base, 0)
  1624. if offset is not None:
  1625. self._offset = offset
  1626. def __call__(self):
  1627. """Return the locations of the ticks."""
  1628. vmin, vmax = self.axis.get_view_interval()
  1629. return self.tick_values(vmin, vmax)
  1630. def tick_values(self, vmin, vmax):
  1631. if vmax < vmin:
  1632. vmin, vmax = vmax, vmin
  1633. step = self._edge.step
  1634. vmin -= self._offset
  1635. vmax -= self._offset
  1636. vmin = self._edge.ge(vmin) * step
  1637. n = (vmax - vmin + 0.001 * step) // step
  1638. locs = vmin - step + np.arange(n + 3) * step + self._offset
  1639. return self.raise_if_exceeds(locs)
  1640. def view_limits(self, dmin, dmax):
  1641. """
  1642. Set the view limits to the nearest tick values that contain the data.
  1643. """
  1644. if mpl.rcParams['axes.autolimit_mode'] == 'round_numbers':
  1645. vmin = self._edge.le(dmin - self._offset) * self._edge.step + self._offset
  1646. vmax = self._edge.ge(dmax - self._offset) * self._edge.step + self._offset
  1647. if vmin == vmax:
  1648. vmin -= 1
  1649. vmax += 1
  1650. else:
  1651. vmin = dmin
  1652. vmax = dmax
  1653. return mtransforms.nonsingular(vmin, vmax)
  1654. def scale_range(vmin, vmax, n=1, threshold=100):
  1655. dv = abs(vmax - vmin) # > 0 as nonsingular is called before.
  1656. meanv = (vmax + vmin) / 2
  1657. if abs(meanv) / dv < threshold:
  1658. offset = 0
  1659. else:
  1660. offset = math.copysign(10 ** (math.log10(abs(meanv)) // 1), meanv)
  1661. scale = 10 ** (math.log10(dv / n) // 1)
  1662. return scale, offset
  1663. class _Edge_integer:
  1664. """
  1665. Helper for `.MaxNLocator`, `.MultipleLocator`, etc.
  1666. Take floating-point precision limitations into account when calculating
  1667. tick locations as integer multiples of a step.
  1668. """
  1669. def __init__(self, step, offset):
  1670. """
  1671. Parameters
  1672. ----------
  1673. step : float > 0
  1674. Interval between ticks.
  1675. offset : float
  1676. Offset subtracted from the data limits prior to calculating tick
  1677. locations.
  1678. """
  1679. if step <= 0:
  1680. raise ValueError("'step' must be positive")
  1681. self.step = step
  1682. self._offset = abs(offset)
  1683. def closeto(self, ms, edge):
  1684. # Allow more slop when the offset is large compared to the step.
  1685. if self._offset > 0:
  1686. digits = np.log10(self._offset / self.step)
  1687. tol = max(1e-10, 10 ** (digits - 12))
  1688. tol = min(0.4999, tol)
  1689. else:
  1690. tol = 1e-10
  1691. return abs(ms - edge) < tol
  1692. def le(self, x):
  1693. """Return the largest n: n*step <= x."""
  1694. d, m = divmod(x, self.step)
  1695. if self.closeto(m / self.step, 1):
  1696. return d + 1
  1697. return d
  1698. def ge(self, x):
  1699. """Return the smallest n: n*step >= x."""
  1700. d, m = divmod(x, self.step)
  1701. if self.closeto(m / self.step, 0):
  1702. return d
  1703. return d + 1
  1704. class MaxNLocator(Locator):
  1705. """
  1706. Place evenly spaced ticks, with a cap on the total number of ticks.
  1707. Finds nice tick locations with no more than :math:`nbins + 1` ticks being within the
  1708. view limits. Locations beyond the limits are added to support autoscaling.
  1709. """
  1710. default_params = dict(nbins=10,
  1711. steps=None,
  1712. integer=False,
  1713. symmetric=False,
  1714. prune=None,
  1715. min_n_ticks=2)
  1716. def __init__(self, nbins=None, **kwargs):
  1717. """
  1718. Parameters
  1719. ----------
  1720. nbins : int or 'auto', default: 10
  1721. Maximum number of intervals; one less than max number of
  1722. ticks. If the string 'auto', the number of bins will be
  1723. automatically determined based on the length of the axis.
  1724. steps : array-like, optional
  1725. Sequence of acceptable tick multiples, starting with 1 and
  1726. ending with 10. For example, if ``steps=[1, 2, 4, 5, 10]``,
  1727. ``20, 40, 60`` or ``0.4, 0.6, 0.8`` would be possible
  1728. sets of ticks because they are multiples of 2.
  1729. ``30, 60, 90`` would not be generated because 3 does not
  1730. appear in this example list of steps.
  1731. integer : bool, default: False
  1732. If True, ticks will take only integer values, provided at least
  1733. *min_n_ticks* integers are found within the view limits.
  1734. symmetric : bool, default: False
  1735. If True, autoscaling will result in a range symmetric about zero.
  1736. prune : {'lower', 'upper', 'both', None}, default: None
  1737. Remove the 'lower' tick, the 'upper' tick, or ticks on 'both' sides
  1738. *if they fall exactly on an axis' edge* (this typically occurs when
  1739. :rc:`axes.autolimit_mode` is 'round_numbers'). Removing such ticks
  1740. is mostly useful for stacked or ganged plots, where the upper tick
  1741. of an Axes overlaps with the lower tick of the axes above it.
  1742. min_n_ticks : int, default: 2
  1743. Relax *nbins* and *integer* constraints if necessary to obtain
  1744. this minimum number of ticks.
  1745. """
  1746. if nbins is not None:
  1747. kwargs['nbins'] = nbins
  1748. self.set_params(**{**self.default_params, **kwargs})
  1749. @staticmethod
  1750. def _validate_steps(steps):
  1751. if not np.iterable(steps):
  1752. raise ValueError('steps argument must be an increasing sequence '
  1753. 'of numbers between 1 and 10 inclusive')
  1754. steps = np.asarray(steps)
  1755. if np.any(np.diff(steps) <= 0) or steps[-1] > 10 or steps[0] < 1:
  1756. raise ValueError('steps argument must be an increasing sequence '
  1757. 'of numbers between 1 and 10 inclusive')
  1758. if steps[0] != 1:
  1759. steps = np.concatenate([[1], steps])
  1760. if steps[-1] != 10:
  1761. steps = np.concatenate([steps, [10]])
  1762. return steps
  1763. @staticmethod
  1764. def _staircase(steps):
  1765. # Make an extended staircase within which the needed step will be
  1766. # found. This is probably much larger than necessary.
  1767. return np.concatenate([0.1 * steps[:-1], steps, [10 * steps[1]]])
  1768. def set_params(self, **kwargs):
  1769. """
  1770. Set parameters for this locator.
  1771. Parameters
  1772. ----------
  1773. nbins : int or 'auto', optional
  1774. see `.MaxNLocator`
  1775. steps : array-like, optional
  1776. see `.MaxNLocator`
  1777. integer : bool, optional
  1778. see `.MaxNLocator`
  1779. symmetric : bool, optional
  1780. see `.MaxNLocator`
  1781. prune : {'lower', 'upper', 'both', None}, optional
  1782. see `.MaxNLocator`
  1783. min_n_ticks : int, optional
  1784. see `.MaxNLocator`
  1785. """
  1786. if 'nbins' in kwargs:
  1787. self._nbins = kwargs.pop('nbins')
  1788. if self._nbins != 'auto':
  1789. self._nbins = int(self._nbins)
  1790. if 'symmetric' in kwargs:
  1791. self._symmetric = kwargs.pop('symmetric')
  1792. if 'prune' in kwargs:
  1793. prune = kwargs.pop('prune')
  1794. _api.check_in_list(['upper', 'lower', 'both', None], prune=prune)
  1795. self._prune = prune
  1796. if 'min_n_ticks' in kwargs:
  1797. self._min_n_ticks = max(1, kwargs.pop('min_n_ticks'))
  1798. if 'steps' in kwargs:
  1799. steps = kwargs.pop('steps')
  1800. if steps is None:
  1801. self._steps = np.array([1, 1.5, 2, 2.5, 3, 4, 5, 6, 8, 10])
  1802. else:
  1803. self._steps = self._validate_steps(steps)
  1804. self._extended_steps = self._staircase(self._steps)
  1805. if 'integer' in kwargs:
  1806. self._integer = kwargs.pop('integer')
  1807. if kwargs:
  1808. raise _api.kwarg_error("set_params", kwargs)
  1809. def _raw_ticks(self, vmin, vmax):
  1810. """
  1811. Generate a list of tick locations including the range *vmin* to
  1812. *vmax*. In some applications, one or both of the end locations
  1813. will not be needed, in which case they are trimmed off
  1814. elsewhere.
  1815. """
  1816. if self._nbins == 'auto':
  1817. if self.axis is not None:
  1818. nbins = np.clip(self.axis.get_tick_space(),
  1819. max(1, self._min_n_ticks - 1), 9)
  1820. else:
  1821. nbins = 9
  1822. else:
  1823. nbins = self._nbins
  1824. scale, offset = scale_range(vmin, vmax, nbins)
  1825. _vmin = vmin - offset
  1826. _vmax = vmax - offset
  1827. steps = self._extended_steps * scale
  1828. if self._integer:
  1829. # For steps > 1, keep only integer values.
  1830. igood = (steps < 1) | (np.abs(steps - np.round(steps)) < 0.001)
  1831. steps = steps[igood]
  1832. raw_step = ((_vmax - _vmin) / nbins)
  1833. if hasattr(self.axis, "axes") and self.axis.axes.name == '3d':
  1834. # Due to the change in automargin behavior in mpl3.9, we need to
  1835. # adjust the raw step to match the mpl3.8 appearance. The zoom
  1836. # factor of 2/48, gives us the 23/24 modifier.
  1837. raw_step = raw_step * 23/24
  1838. large_steps = steps >= raw_step
  1839. if mpl.rcParams['axes.autolimit_mode'] == 'round_numbers':
  1840. # Classic round_numbers mode may require a larger step.
  1841. # Get first multiple of steps that are <= _vmin
  1842. floored_vmins = (_vmin // steps) * steps
  1843. floored_vmaxs = floored_vmins + steps * nbins
  1844. large_steps = large_steps & (floored_vmaxs >= _vmax)
  1845. # Find index of smallest large step
  1846. if any(large_steps):
  1847. istep = np.nonzero(large_steps)[0][0]
  1848. else:
  1849. istep = len(steps) - 1
  1850. # Start at smallest of the steps greater than the raw step, and check
  1851. # if it provides enough ticks. If not, work backwards through
  1852. # smaller steps until one is found that provides enough ticks.
  1853. for step in steps[:istep+1][::-1]:
  1854. if (self._integer and
  1855. np.floor(_vmax) - np.ceil(_vmin) >= self._min_n_ticks - 1):
  1856. step = max(1, step)
  1857. best_vmin = (_vmin // step) * step
  1858. # Find tick locations spanning the vmin-vmax range, taking into
  1859. # account degradation of precision when there is a large offset.
  1860. # The edge ticks beyond vmin and/or vmax are needed for the
  1861. # "round_numbers" autolimit mode.
  1862. edge = _Edge_integer(step, offset)
  1863. low = edge.le(_vmin - best_vmin)
  1864. high = edge.ge(_vmax - best_vmin)
  1865. ticks = np.arange(low, high + 1) * step + best_vmin
  1866. # Count only the ticks that will be displayed.
  1867. nticks = ((ticks <= _vmax) & (ticks >= _vmin)).sum()
  1868. if nticks >= self._min_n_ticks:
  1869. break
  1870. return ticks + offset
  1871. def __call__(self):
  1872. vmin, vmax = self.axis.get_view_interval()
  1873. return self.tick_values(vmin, vmax)
  1874. def tick_values(self, vmin, vmax):
  1875. if self._symmetric:
  1876. vmax = max(abs(vmin), abs(vmax))
  1877. vmin = -vmax
  1878. vmin, vmax = mtransforms.nonsingular(
  1879. vmin, vmax, expander=1e-13, tiny=1e-14)
  1880. locs = self._raw_ticks(vmin, vmax)
  1881. prune = self._prune
  1882. if prune == 'lower':
  1883. locs = locs[1:]
  1884. elif prune == 'upper':
  1885. locs = locs[:-1]
  1886. elif prune == 'both':
  1887. locs = locs[1:-1]
  1888. return self.raise_if_exceeds(locs)
  1889. def view_limits(self, dmin, dmax):
  1890. if self._symmetric:
  1891. dmax = max(abs(dmin), abs(dmax))
  1892. dmin = -dmax
  1893. dmin, dmax = mtransforms.nonsingular(
  1894. dmin, dmax, expander=1e-12, tiny=1e-13)
  1895. if mpl.rcParams['axes.autolimit_mode'] == 'round_numbers':
  1896. return self._raw_ticks(dmin, dmax)[[0, -1]]
  1897. else:
  1898. return dmin, dmax
  1899. def _is_decade(x, *, base=10, rtol=None):
  1900. """Return True if *x* is an integer power of *base*."""
  1901. if not np.isfinite(x):
  1902. return False
  1903. if x == 0.0:
  1904. return True
  1905. lx = np.log(abs(x)) / np.log(base)
  1906. if rtol is None:
  1907. return np.isclose(lx, np.round(lx))
  1908. else:
  1909. return np.isclose(lx, np.round(lx), rtol=rtol)
  1910. def _decade_less_equal(x, base):
  1911. """
  1912. Return the largest integer power of *base* that's less or equal to *x*.
  1913. If *x* is negative, the exponent will be *greater*.
  1914. """
  1915. return (x if x == 0 else
  1916. -_decade_greater_equal(-x, base) if x < 0 else
  1917. base ** np.floor(np.log(x) / np.log(base)))
  1918. def _decade_greater_equal(x, base):
  1919. """
  1920. Return the smallest integer power of *base* that's greater or equal to *x*.
  1921. If *x* is negative, the exponent will be *smaller*.
  1922. """
  1923. return (x if x == 0 else
  1924. -_decade_less_equal(-x, base) if x < 0 else
  1925. base ** np.ceil(np.log(x) / np.log(base)))
  1926. def _decade_less(x, base):
  1927. """
  1928. Return the largest integer power of *base* that's less than *x*.
  1929. If *x* is negative, the exponent will be *greater*.
  1930. """
  1931. if x < 0:
  1932. return -_decade_greater(-x, base)
  1933. less = _decade_less_equal(x, base)
  1934. if less == x:
  1935. less /= base
  1936. return less
  1937. def _decade_greater(x, base):
  1938. """
  1939. Return the smallest integer power of *base* that's greater than *x*.
  1940. If *x* is negative, the exponent will be *smaller*.
  1941. """
  1942. if x < 0:
  1943. return -_decade_less(-x, base)
  1944. greater = _decade_greater_equal(x, base)
  1945. if greater == x:
  1946. greater *= base
  1947. return greater
  1948. def _is_close_to_int(x):
  1949. return math.isclose(x, round(x))
  1950. class LogLocator(Locator):
  1951. """
  1952. Place logarithmically spaced ticks.
  1953. Places ticks at the values ``subs[j] * base**i``.
  1954. """
  1955. def __init__(self, base=10.0, subs=(1.0,), *, numticks=None):
  1956. """
  1957. Parameters
  1958. ----------
  1959. base : float, default: 10.0
  1960. The base of the log used, so major ticks are placed at ``base**n``, where
  1961. ``n`` is an integer.
  1962. subs : None or {'auto', 'all'} or sequence of float, default: (1.0,)
  1963. Gives the multiples of integer powers of the base at which to place ticks.
  1964. The default of ``(1.0, )`` places ticks only at integer powers of the base.
  1965. Permitted string values are ``'auto'`` and ``'all'``. Both of these use an
  1966. algorithm based on the axis view limits to determine whether and how to put
  1967. ticks between integer powers of the base:
  1968. - ``'auto'``: Ticks are placed only between integer powers.
  1969. - ``'all'``: Ticks are placed between *and* at integer powers.
  1970. - ``None``: Equivalent to ``'auto'``.
  1971. numticks : None or int, default: None
  1972. The maximum number of ticks to allow on a given axis. The default of
  1973. ``None`` will try to choose intelligently as long as this Locator has
  1974. already been assigned to an axis using `~.axis.Axis.get_tick_space`, but
  1975. otherwise falls back to 9.
  1976. """
  1977. if numticks is None:
  1978. if mpl.rcParams['_internal.classic_mode']:
  1979. numticks = 15
  1980. else:
  1981. numticks = 'auto'
  1982. self._base = float(base)
  1983. self._set_subs(subs)
  1984. self.numticks = numticks
  1985. def set_params(self, base=None, subs=None, *, numticks=None):
  1986. """Set parameters within this locator."""
  1987. if base is not None:
  1988. self._base = float(base)
  1989. if subs is not None:
  1990. self._set_subs(subs)
  1991. if numticks is not None:
  1992. self.numticks = numticks
  1993. def _set_subs(self, subs):
  1994. """
  1995. Set the minor ticks for the log scaling every ``base**i*subs[j]``.
  1996. """
  1997. if subs is None: # consistency with previous bad API
  1998. self._subs = 'auto'
  1999. elif isinstance(subs, str):
  2000. _api.check_in_list(('all', 'auto'), subs=subs)
  2001. self._subs = subs
  2002. else:
  2003. try:
  2004. self._subs = np.asarray(subs, dtype=float)
  2005. except ValueError as e:
  2006. raise ValueError("subs must be None, 'all', 'auto' or "
  2007. "a sequence of floats, not "
  2008. f"{subs}.") from e
  2009. if self._subs.ndim != 1:
  2010. raise ValueError("A sequence passed to subs must be "
  2011. "1-dimensional, not "
  2012. f"{self._subs.ndim}-dimensional.")
  2013. def __call__(self):
  2014. """Return the locations of the ticks."""
  2015. vmin, vmax = self.axis.get_view_interval()
  2016. return self.tick_values(vmin, vmax)
  2017. def tick_values(self, vmin, vmax):
  2018. if self.numticks == 'auto':
  2019. if self.axis is not None:
  2020. numticks = np.clip(self.axis.get_tick_space(), 2, 9)
  2021. else:
  2022. numticks = 9
  2023. else:
  2024. numticks = self.numticks
  2025. b = self._base
  2026. if vmin <= 0.0:
  2027. if self.axis is not None:
  2028. vmin = self.axis.get_minpos()
  2029. if vmin <= 0.0 or not np.isfinite(vmin):
  2030. raise ValueError(
  2031. "Data has no positive values, and therefore cannot be log-scaled.")
  2032. _log.debug('vmin %s vmax %s', vmin, vmax)
  2033. if vmax < vmin:
  2034. vmin, vmax = vmax, vmin
  2035. log_vmin = math.log(vmin) / math.log(b)
  2036. log_vmax = math.log(vmax) / math.log(b)
  2037. numdec = math.floor(log_vmax) - math.ceil(log_vmin)
  2038. if isinstance(self._subs, str):
  2039. if numdec > 10 or b < 3:
  2040. if self._subs == 'auto':
  2041. return np.array([]) # no minor or major ticks
  2042. else:
  2043. subs = np.array([1.0]) # major ticks
  2044. else:
  2045. _first = 2.0 if self._subs == 'auto' else 1.0
  2046. subs = np.arange(_first, b)
  2047. else:
  2048. subs = self._subs
  2049. # Get decades between major ticks.
  2050. stride = (max(math.ceil(numdec / (numticks - 1)), 1)
  2051. if mpl.rcParams['_internal.classic_mode'] else
  2052. numdec // numticks + 1)
  2053. # if we have decided that the stride is as big or bigger than
  2054. # the range, clip the stride back to the available range - 1
  2055. # with a floor of 1. This prevents getting axis with only 1 tick
  2056. # visible.
  2057. if stride >= numdec:
  2058. stride = max(1, numdec - 1)
  2059. # Does subs include anything other than 1? Essentially a hack to know
  2060. # whether we're a major or a minor locator.
  2061. have_subs = len(subs) > 1 or (len(subs) == 1 and subs[0] != 1.0)
  2062. decades = np.arange(math.floor(log_vmin) - stride,
  2063. math.ceil(log_vmax) + 2 * stride, stride)
  2064. if have_subs:
  2065. if stride == 1:
  2066. ticklocs = np.concatenate(
  2067. [subs * decade_start for decade_start in b ** decades])
  2068. else:
  2069. ticklocs = np.array([])
  2070. else:
  2071. ticklocs = b ** decades
  2072. _log.debug('ticklocs %r', ticklocs)
  2073. if (len(subs) > 1
  2074. and stride == 1
  2075. and ((vmin <= ticklocs) & (ticklocs <= vmax)).sum() <= 1):
  2076. # If we're a minor locator *that expects at least two ticks per
  2077. # decade* and the major locator stride is 1 and there's no more
  2078. # than one minor tick, switch to AutoLocator.
  2079. return AutoLocator().tick_values(vmin, vmax)
  2080. else:
  2081. return self.raise_if_exceeds(ticklocs)
  2082. def view_limits(self, vmin, vmax):
  2083. """Try to choose the view limits intelligently."""
  2084. b = self._base
  2085. vmin, vmax = self.nonsingular(vmin, vmax)
  2086. if mpl.rcParams['axes.autolimit_mode'] == 'round_numbers':
  2087. vmin = _decade_less_equal(vmin, b)
  2088. vmax = _decade_greater_equal(vmax, b)
  2089. return vmin, vmax
  2090. def nonsingular(self, vmin, vmax):
  2091. if vmin > vmax:
  2092. vmin, vmax = vmax, vmin
  2093. if not np.isfinite(vmin) or not np.isfinite(vmax):
  2094. vmin, vmax = 1, 10 # Initial range, no data plotted yet.
  2095. elif vmax <= 0:
  2096. _api.warn_external(
  2097. "Data has no positive values, and therefore cannot be "
  2098. "log-scaled.")
  2099. vmin, vmax = 1, 10
  2100. else:
  2101. # Consider shared axises
  2102. minpos = min(axis.get_minpos() for axis in self.axis._get_shared_axis())
  2103. if not np.isfinite(minpos):
  2104. minpos = 1e-300 # This should never take effect.
  2105. if vmin <= 0:
  2106. vmin = minpos
  2107. if vmin == vmax:
  2108. vmin = _decade_less(vmin, self._base)
  2109. vmax = _decade_greater(vmax, self._base)
  2110. return vmin, vmax
  2111. class SymmetricalLogLocator(Locator):
  2112. """
  2113. Place ticks spaced linearly near zero and spaced logarithmically beyond a threshold.
  2114. """
  2115. def __init__(self, transform=None, subs=None, linthresh=None, base=None):
  2116. """
  2117. Parameters
  2118. ----------
  2119. transform : `~.scale.SymmetricalLogTransform`, optional
  2120. If set, defines the *base* and *linthresh* of the symlog transform.
  2121. base, linthresh : float, optional
  2122. The *base* and *linthresh* of the symlog transform, as documented
  2123. for `.SymmetricalLogScale`. These parameters are only used if
  2124. *transform* is not set.
  2125. subs : sequence of float, default: [1]
  2126. The multiples of integer powers of the base where ticks are placed,
  2127. i.e., ticks are placed at
  2128. ``[sub * base**i for i in ... for sub in subs]``.
  2129. Notes
  2130. -----
  2131. Either *transform*, or both *base* and *linthresh*, must be given.
  2132. """
  2133. if transform is not None:
  2134. self._base = transform.base
  2135. self._linthresh = transform.linthresh
  2136. elif linthresh is not None and base is not None:
  2137. self._base = base
  2138. self._linthresh = linthresh
  2139. else:
  2140. raise ValueError("Either transform, or both linthresh "
  2141. "and base, must be provided.")
  2142. if subs is None:
  2143. self._subs = [1.0]
  2144. else:
  2145. self._subs = subs
  2146. self.numticks = 15
  2147. def set_params(self, subs=None, numticks=None):
  2148. """Set parameters within this locator."""
  2149. if numticks is not None:
  2150. self.numticks = numticks
  2151. if subs is not None:
  2152. self._subs = subs
  2153. def __call__(self):
  2154. """Return the locations of the ticks."""
  2155. # Note, these are untransformed coordinates
  2156. vmin, vmax = self.axis.get_view_interval()
  2157. return self.tick_values(vmin, vmax)
  2158. def tick_values(self, vmin, vmax):
  2159. linthresh = self._linthresh
  2160. if vmax < vmin:
  2161. vmin, vmax = vmax, vmin
  2162. # The domain is divided into three sections, only some of
  2163. # which may actually be present.
  2164. #
  2165. # <======== -t ==0== t ========>
  2166. # aaaaaaaaa bbbbb ccccccccc
  2167. #
  2168. # a) and c) will have ticks at integral log positions. The
  2169. # number of ticks needs to be reduced if there are more
  2170. # than self.numticks of them.
  2171. #
  2172. # b) has a tick at 0 and only 0 (we assume t is a small
  2173. # number, and the linear segment is just an implementation
  2174. # detail and not interesting.)
  2175. #
  2176. # We could also add ticks at t, but that seems to usually be
  2177. # uninteresting.
  2178. #
  2179. # "simple" mode is when the range falls entirely within [-t, t]
  2180. # -- it should just display (vmin, 0, vmax)
  2181. if -linthresh <= vmin < vmax <= linthresh:
  2182. # only the linear range is present
  2183. return sorted({vmin, 0, vmax})
  2184. # Lower log range is present
  2185. has_a = (vmin < -linthresh)
  2186. # Upper log range is present
  2187. has_c = (vmax > linthresh)
  2188. # Check if linear range is present
  2189. has_b = (has_a and vmax > -linthresh) or (has_c and vmin < linthresh)
  2190. base = self._base
  2191. def get_log_range(lo, hi):
  2192. lo = np.floor(np.log(lo) / np.log(base))
  2193. hi = np.ceil(np.log(hi) / np.log(base))
  2194. return lo, hi
  2195. # Calculate all the ranges, so we can determine striding
  2196. a_lo, a_hi = (0, 0)
  2197. if has_a:
  2198. a_upper_lim = min(-linthresh, vmax)
  2199. a_lo, a_hi = get_log_range(abs(a_upper_lim), abs(vmin) + 1)
  2200. c_lo, c_hi = (0, 0)
  2201. if has_c:
  2202. c_lower_lim = max(linthresh, vmin)
  2203. c_lo, c_hi = get_log_range(c_lower_lim, vmax + 1)
  2204. # Calculate the total number of integer exponents in a and c ranges
  2205. total_ticks = (a_hi - a_lo) + (c_hi - c_lo)
  2206. if has_b:
  2207. total_ticks += 1
  2208. stride = max(total_ticks // (self.numticks - 1), 1)
  2209. decades = []
  2210. if has_a:
  2211. decades.extend(-1 * (base ** (np.arange(a_lo, a_hi,
  2212. stride)[::-1])))
  2213. if has_b:
  2214. decades.append(0.0)
  2215. if has_c:
  2216. decades.extend(base ** (np.arange(c_lo, c_hi, stride)))
  2217. subs = np.asarray(self._subs)
  2218. if len(subs) > 1 or subs[0] != 1.0:
  2219. ticklocs = []
  2220. for decade in decades:
  2221. if decade == 0:
  2222. ticklocs.append(decade)
  2223. else:
  2224. ticklocs.extend(subs * decade)
  2225. else:
  2226. ticklocs = decades
  2227. return self.raise_if_exceeds(np.array(ticklocs))
  2228. def view_limits(self, vmin, vmax):
  2229. """Try to choose the view limits intelligently."""
  2230. b = self._base
  2231. if vmax < vmin:
  2232. vmin, vmax = vmax, vmin
  2233. if mpl.rcParams['axes.autolimit_mode'] == 'round_numbers':
  2234. vmin = _decade_less_equal(vmin, b)
  2235. vmax = _decade_greater_equal(vmax, b)
  2236. if vmin == vmax:
  2237. vmin = _decade_less(vmin, b)
  2238. vmax = _decade_greater(vmax, b)
  2239. return mtransforms.nonsingular(vmin, vmax)
  2240. class AsinhLocator(Locator):
  2241. """
  2242. Place ticks spaced evenly on an inverse-sinh scale.
  2243. Generally used with the `~.scale.AsinhScale` class.
  2244. .. note::
  2245. This API is provisional and may be revised in the future
  2246. based on early user feedback.
  2247. """
  2248. def __init__(self, linear_width, numticks=11, symthresh=0.2,
  2249. base=10, subs=None):
  2250. """
  2251. Parameters
  2252. ----------
  2253. linear_width : float
  2254. The scale parameter defining the extent
  2255. of the quasi-linear region.
  2256. numticks : int, default: 11
  2257. The approximate number of major ticks that will fit
  2258. along the entire axis
  2259. symthresh : float, default: 0.2
  2260. The fractional threshold beneath which data which covers
  2261. a range that is approximately symmetric about zero
  2262. will have ticks that are exactly symmetric.
  2263. base : int, default: 10
  2264. The number base used for rounding tick locations
  2265. on a logarithmic scale. If this is less than one,
  2266. then rounding is to the nearest integer multiple
  2267. of powers of ten.
  2268. subs : tuple, default: None
  2269. Multiples of the number base, typically used
  2270. for the minor ticks, e.g. (2, 5) when base=10.
  2271. """
  2272. super().__init__()
  2273. self.linear_width = linear_width
  2274. self.numticks = numticks
  2275. self.symthresh = symthresh
  2276. self.base = base
  2277. self.subs = subs
  2278. def set_params(self, numticks=None, symthresh=None,
  2279. base=None, subs=None):
  2280. """Set parameters within this locator."""
  2281. if numticks is not None:
  2282. self.numticks = numticks
  2283. if symthresh is not None:
  2284. self.symthresh = symthresh
  2285. if base is not None:
  2286. self.base = base
  2287. if subs is not None:
  2288. self.subs = subs if len(subs) > 0 else None
  2289. def __call__(self):
  2290. vmin, vmax = self.axis.get_view_interval()
  2291. if (vmin * vmax) < 0 and abs(1 + vmax / vmin) < self.symthresh:
  2292. # Data-range appears to be almost symmetric, so round up:
  2293. bound = max(abs(vmin), abs(vmax))
  2294. return self.tick_values(-bound, bound)
  2295. else:
  2296. return self.tick_values(vmin, vmax)
  2297. def tick_values(self, vmin, vmax):
  2298. # Construct a set of uniformly-spaced "on-screen" locations.
  2299. ymin, ymax = self.linear_width * np.arcsinh(np.array([vmin, vmax])
  2300. / self.linear_width)
  2301. ys = np.linspace(ymin, ymax, self.numticks)
  2302. zero_dev = abs(ys / (ymax - ymin))
  2303. if ymin * ymax < 0:
  2304. # Ensure that the zero tick-mark is included, if the axis straddles zero.
  2305. ys = np.hstack([ys[(zero_dev > 0.5 / self.numticks)], 0.0])
  2306. # Transform the "on-screen" grid to the data space:
  2307. xs = self.linear_width * np.sinh(ys / self.linear_width)
  2308. zero_xs = (ys == 0)
  2309. # Round the data-space values to be intuitive base-n numbers, keeping track of
  2310. # positive and negative values separately and carefully treating the zero value.
  2311. with np.errstate(divide="ignore"): # base ** log(0) = base ** -inf = 0.
  2312. if self.base > 1:
  2313. pows = (np.sign(xs)
  2314. * self.base ** np.floor(np.log(abs(xs)) / math.log(self.base)))
  2315. qs = np.outer(pows, self.subs).flatten() if self.subs else pows
  2316. else: # No need to adjust sign(pows), as it cancels out when computing qs.
  2317. pows = np.where(zero_xs, 1, 10**np.floor(np.log10(abs(xs))))
  2318. qs = pows * np.round(xs / pows)
  2319. ticks = np.array(sorted(set(qs)))
  2320. return ticks if len(ticks) >= 2 else np.linspace(vmin, vmax, self.numticks)
  2321. class LogitLocator(MaxNLocator):
  2322. """
  2323. Place ticks spaced evenly on a logit scale.
  2324. """
  2325. def __init__(self, minor=False, *, nbins="auto"):
  2326. """
  2327. Parameters
  2328. ----------
  2329. nbins : int or 'auto', optional
  2330. Number of ticks. Only used if minor is False.
  2331. minor : bool, default: False
  2332. Indicate if this locator is for minor ticks or not.
  2333. """
  2334. self._minor = minor
  2335. super().__init__(nbins=nbins, steps=[1, 2, 5, 10])
  2336. def set_params(self, minor=None, **kwargs):
  2337. """Set parameters within this locator."""
  2338. if minor is not None:
  2339. self._minor = minor
  2340. super().set_params(**kwargs)
  2341. @property
  2342. def minor(self):
  2343. return self._minor
  2344. @minor.setter
  2345. def minor(self, value):
  2346. self.set_params(minor=value)
  2347. def tick_values(self, vmin, vmax):
  2348. # dummy axis has no axes attribute
  2349. if hasattr(self.axis, "axes") and self.axis.axes.name == "polar":
  2350. raise NotImplementedError("Polar axis cannot be logit scaled yet")
  2351. if self._nbins == "auto":
  2352. if self.axis is not None:
  2353. nbins = self.axis.get_tick_space()
  2354. if nbins < 2:
  2355. nbins = 2
  2356. else:
  2357. nbins = 9
  2358. else:
  2359. nbins = self._nbins
  2360. # We define ideal ticks with their index:
  2361. # linscale: ... 1e-3 1e-2 1e-1 1/2 1-1e-1 1-1e-2 1-1e-3 ...
  2362. # b-scale : ... -3 -2 -1 0 1 2 3 ...
  2363. def ideal_ticks(x):
  2364. return 10 ** x if x < 0 else 1 - (10 ** (-x)) if x > 0 else 0.5
  2365. vmin, vmax = self.nonsingular(vmin, vmax)
  2366. binf = int(
  2367. np.floor(np.log10(vmin))
  2368. if vmin < 0.5
  2369. else 0
  2370. if vmin < 0.9
  2371. else -np.ceil(np.log10(1 - vmin))
  2372. )
  2373. bsup = int(
  2374. np.ceil(np.log10(vmax))
  2375. if vmax <= 0.5
  2376. else 1
  2377. if vmax <= 0.9
  2378. else -np.floor(np.log10(1 - vmax))
  2379. )
  2380. numideal = bsup - binf - 1
  2381. if numideal >= 2:
  2382. # have 2 or more wanted ideal ticks, so use them as major ticks
  2383. if numideal > nbins:
  2384. # to many ideal ticks, subsampling ideals for major ticks, and
  2385. # take others for minor ticks
  2386. subsampling_factor = math.ceil(numideal / nbins)
  2387. if self._minor:
  2388. ticklocs = [
  2389. ideal_ticks(b)
  2390. for b in range(binf, bsup + 1)
  2391. if (b % subsampling_factor) != 0
  2392. ]
  2393. else:
  2394. ticklocs = [
  2395. ideal_ticks(b)
  2396. for b in range(binf, bsup + 1)
  2397. if (b % subsampling_factor) == 0
  2398. ]
  2399. return self.raise_if_exceeds(np.array(ticklocs))
  2400. if self._minor:
  2401. ticklocs = []
  2402. for b in range(binf, bsup):
  2403. if b < -1:
  2404. ticklocs.extend(np.arange(2, 10) * 10 ** b)
  2405. elif b == -1:
  2406. ticklocs.extend(np.arange(2, 5) / 10)
  2407. elif b == 0:
  2408. ticklocs.extend(np.arange(6, 9) / 10)
  2409. else:
  2410. ticklocs.extend(
  2411. 1 - np.arange(2, 10)[::-1] * 10 ** (-b - 1)
  2412. )
  2413. return self.raise_if_exceeds(np.array(ticklocs))
  2414. ticklocs = [ideal_ticks(b) for b in range(binf, bsup + 1)]
  2415. return self.raise_if_exceeds(np.array(ticklocs))
  2416. # the scale is zoomed so same ticks as linear scale can be used
  2417. if self._minor:
  2418. return []
  2419. return super().tick_values(vmin, vmax)
  2420. def nonsingular(self, vmin, vmax):
  2421. standard_minpos = 1e-7
  2422. initial_range = (standard_minpos, 1 - standard_minpos)
  2423. if vmin > vmax:
  2424. vmin, vmax = vmax, vmin
  2425. if not np.isfinite(vmin) or not np.isfinite(vmax):
  2426. vmin, vmax = initial_range # Initial range, no data plotted yet.
  2427. elif vmax <= 0 or vmin >= 1:
  2428. # vmax <= 0 occurs when all values are negative
  2429. # vmin >= 1 occurs when all values are greater than one
  2430. _api.warn_external(
  2431. "Data has no values between 0 and 1, and therefore cannot be "
  2432. "logit-scaled."
  2433. )
  2434. vmin, vmax = initial_range
  2435. else:
  2436. minpos = (
  2437. self.axis.get_minpos()
  2438. if self.axis is not None
  2439. else standard_minpos
  2440. )
  2441. if not np.isfinite(minpos):
  2442. minpos = standard_minpos # This should never take effect.
  2443. if vmin <= 0:
  2444. vmin = minpos
  2445. # NOTE: for vmax, we should query a property similar to get_minpos,
  2446. # but related to the maximal, less-than-one data point.
  2447. # Unfortunately, Bbox._minpos is defined very deep in the BBox and
  2448. # updated with data, so for now we use 1 - minpos as a substitute.
  2449. if vmax >= 1:
  2450. vmax = 1 - minpos
  2451. if vmin == vmax:
  2452. vmin, vmax = 0.1 * vmin, 1 - 0.1 * vmin
  2453. return vmin, vmax
  2454. class AutoLocator(MaxNLocator):
  2455. """
  2456. Place evenly spaced ticks, with the step size and maximum number of ticks chosen
  2457. automatically.
  2458. This is a subclass of `~matplotlib.ticker.MaxNLocator`, with parameters
  2459. *nbins = 'auto'* and *steps = [1, 2, 2.5, 5, 10]*.
  2460. """
  2461. def __init__(self):
  2462. """
  2463. To know the values of the non-public parameters, please have a
  2464. look to the defaults of `~matplotlib.ticker.MaxNLocator`.
  2465. """
  2466. if mpl.rcParams['_internal.classic_mode']:
  2467. nbins = 9
  2468. steps = [1, 2, 5, 10]
  2469. else:
  2470. nbins = 'auto'
  2471. steps = [1, 2, 2.5, 5, 10]
  2472. super().__init__(nbins=nbins, steps=steps)
  2473. class AutoMinorLocator(Locator):
  2474. """
  2475. Place evenly spaced minor ticks, with the step size and maximum number of ticks
  2476. chosen automatically.
  2477. The Axis must use a linear scale and have evenly spaced major ticks.
  2478. """
  2479. def __init__(self, n=None):
  2480. """
  2481. Parameters
  2482. ----------
  2483. n : int or 'auto', default: :rc:`xtick.minor.ndivs` or :rc:`ytick.minor.ndivs`
  2484. The number of subdivisions of the interval between major ticks;
  2485. e.g., n=2 will place a single minor tick midway between major ticks.
  2486. If *n* is 'auto', it will be set to 4 or 5: if the distance
  2487. between the major ticks equals 1, 2.5, 5 or 10 it can be perfectly
  2488. divided in 5 equidistant sub-intervals with a length multiple of
  2489. 0.05; otherwise, it is divided in 4 sub-intervals.
  2490. """
  2491. self.ndivs = n
  2492. def __call__(self):
  2493. # docstring inherited
  2494. if self.axis.get_scale() == 'log':
  2495. _api.warn_external('AutoMinorLocator does not work on logarithmic scales')
  2496. return []
  2497. majorlocs = np.unique(self.axis.get_majorticklocs())
  2498. if len(majorlocs) < 2:
  2499. # Need at least two major ticks to find minor tick locations.
  2500. # TODO: Figure out a way to still be able to display minor ticks with less
  2501. # than two major ticks visible. For now, just display no ticks at all.
  2502. return []
  2503. majorstep = majorlocs[1] - majorlocs[0]
  2504. if self.ndivs is None:
  2505. self.ndivs = mpl.rcParams[
  2506. 'ytick.minor.ndivs' if self.axis.axis_name == 'y'
  2507. else 'xtick.minor.ndivs'] # for x and z axis
  2508. if self.ndivs == 'auto':
  2509. majorstep_mantissa = 10 ** (np.log10(majorstep) % 1)
  2510. ndivs = 5 if np.isclose(majorstep_mantissa, [1, 2.5, 5, 10]).any() else 4
  2511. else:
  2512. ndivs = self.ndivs
  2513. minorstep = majorstep / ndivs
  2514. vmin, vmax = sorted(self.axis.get_view_interval())
  2515. t0 = majorlocs[0]
  2516. tmin = round((vmin - t0) / minorstep)
  2517. tmax = round((vmax - t0) / minorstep) + 1
  2518. locs = (np.arange(tmin, tmax) * minorstep) + t0
  2519. return self.raise_if_exceeds(locs)
  2520. def tick_values(self, vmin, vmax):
  2521. raise NotImplementedError(
  2522. f"Cannot get tick locations for a {type(self).__name__}")