12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981 |
- """
- Tick locating and formatting
- ============================
- This module contains classes to support completely configurable tick
- locating and formatting. Although the locators know nothing about major
- or minor ticks, they are used by the Axis class to support major and
- minor tick locating and formatting. Generic tick locators and
- formatters are provided, as well as domain specific custom ones.
- Default Formatter
- -----------------
- The default formatter identifies when the x-data being plotted is a
- small range on top of a large offset. To reduce the chances that the
- ticklabels overlap, the ticks are labeled as deltas from a fixed offset.
- For example::
- ax.plot(np.arange(2000, 2010), range(10))
- will have tick of 0-9 with an offset of +2e3. If this is not desired
- turn off the use of the offset on the default formatter::
- ax.get_xaxis().get_major_formatter().set_useOffset(False)
- set the rcParam ``axes.formatter.useoffset=False`` to turn it off
- globally, or set a different formatter.
- Tick locating
- -------------
- The Locator class is the base class for all tick locators. The locators
- handle autoscaling of the view limits based on the data limits, and the
- choosing of tick locations. A useful semi-automatic tick locator is
- `MultipleLocator`. It is initialized with a base, e.g., 10, and it picks
- axis limits and ticks that are multiples of that base.
- The Locator subclasses defined here are
- :class:`AutoLocator`
- `MaxNLocator` with simple defaults. This is the default tick locator for
- most plotting.
- :class:`MaxNLocator`
- Finds up to a max number of intervals with ticks at nice locations.
- :class:`LinearLocator`
- Space ticks evenly from min to max.
- :class:`LogLocator`
- Space ticks logarithmically from min to max.
- :class:`MultipleLocator`
- Ticks and range are a multiple of base; either integer or float.
- :class:`FixedLocator`
- Tick locations are fixed.
- :class:`IndexLocator`
- Locator for index plots (e.g., where ``x = range(len(y))``).
- :class:`NullLocator`
- No ticks.
- :class:`SymmetricalLogLocator`
- Locator for use with with the symlog norm; works like `LogLocator` for the
- part outside of the threshold and adds 0 if inside the limits.
- :class:`LogitLocator`
- Locator for logit scaling.
- :class:`OldAutoLocator`
- Choose a `MultipleLocator` and dynamically reassign it for intelligent
- ticking during navigation.
- :class:`AutoMinorLocator`
- Locator for minor ticks when the axis is linear and the
- major ticks are uniformly spaced. Subdivides the major
- tick interval into a specified number of minor intervals,
- defaulting to 4 or 5 depending on the major interval.
- There are a number of locators specialized for date locations - see
- the `dates` module.
- You can define your own locator by deriving from Locator. You must
- override the ``__call__`` method, which returns a sequence of locations,
- and you will probably want to override the autoscale method to set the
- view limits from the data limits.
- If you want to override the default locator, use one of the above or a custom
- locator and pass it to the x or y axis instance. The relevant methods are::
- ax.xaxis.set_major_locator(xmajor_locator)
- ax.xaxis.set_minor_locator(xminor_locator)
- ax.yaxis.set_major_locator(ymajor_locator)
- ax.yaxis.set_minor_locator(yminor_locator)
- The default minor locator is `NullLocator`, i.e., no minor ticks on by default.
- Tick formatting
- ---------------
- Tick formatting is controlled by classes derived from Formatter. The formatter
- operates on a single tick value and returns a string to the axis.
- :class:`NullFormatter`
- No labels on the ticks.
- :class:`IndexFormatter`
- Set the strings from a list of labels.
- :class:`FixedFormatter`
- Set the strings manually for the labels.
- :class:`FuncFormatter`
- User defined function sets the labels.
- :class:`StrMethodFormatter`
- Use string `format` method.
- :class:`FormatStrFormatter`
- Use an old-style sprintf format string.
- :class:`ScalarFormatter`
- Default formatter for scalars: autopick the format string.
- :class:`LogFormatter`
- Formatter for log axes.
- :class:`LogFormatterExponent`
- Format values for log axis using ``exponent = log_base(value)``.
- :class:`LogFormatterMathtext`
- Format values for log axis using ``exponent = log_base(value)``
- using Math text.
- :class:`LogFormatterSciNotation`
- Format values for log axis using scientific notation.
- :class:`LogitFormatter`
- Probability formatter.
- :class:`EngFormatter`
- Format labels in engineering notation
- :class:`PercentFormatter`
- Format labels as a percentage
- You can derive your own formatter from the Formatter base class by
- simply overriding the ``__call__`` method. The formatter class has
- access to the axis view and data limits.
- To control the major and minor tick label formats, use one of the
- following methods::
- ax.xaxis.set_major_formatter(xmajor_formatter)
- ax.xaxis.set_minor_formatter(xminor_formatter)
- ax.yaxis.set_major_formatter(ymajor_formatter)
- ax.yaxis.set_minor_formatter(yminor_formatter)
- See :doc:`/gallery/ticks_and_spines/major_minor_demo` for an
- example of setting major and minor ticks. See the :mod:`matplotlib.dates`
- module for more information and examples of using date locators and formatters.
- """
- import itertools
- import logging
- import locale
- import math
- import numpy as np
- from matplotlib import rcParams
- from matplotlib import cbook
- from matplotlib import transforms as mtransforms
- _log = logging.getLogger(__name__)
- __all__ = ('TickHelper', 'Formatter', 'FixedFormatter',
- 'NullFormatter', 'FuncFormatter', 'FormatStrFormatter',
- 'StrMethodFormatter', 'ScalarFormatter', 'LogFormatter',
- 'LogFormatterExponent', 'LogFormatterMathtext',
- 'IndexFormatter', 'LogFormatterSciNotation',
- 'LogitFormatter', 'EngFormatter', 'PercentFormatter',
- 'OldScalarFormatter',
- 'Locator', 'IndexLocator', 'FixedLocator', 'NullLocator',
- 'LinearLocator', 'LogLocator', 'AutoLocator',
- 'MultipleLocator', 'MaxNLocator', 'AutoMinorLocator',
- 'SymmetricalLogLocator', 'LogitLocator', 'OldAutoLocator')
- def _mathdefault(s):
- return '\\mathdefault{%s}' % s
- class _DummyAxis:
- def __init__(self, minpos=0):
- self.dataLim = mtransforms.Bbox.unit()
- self.viewLim = mtransforms.Bbox.unit()
- self._minpos = minpos
- def get_view_interval(self):
- return self.viewLim.intervalx
- def set_view_interval(self, vmin, vmax):
- self.viewLim.intervalx = vmin, vmax
- def get_minpos(self):
- return self._minpos
- def get_data_interval(self):
- return self.dataLim.intervalx
- def set_data_interval(self, vmin, vmax):
- self.dataLim.intervalx = vmin, vmax
- def get_tick_space(self):
- # Just use the long-standing default of nbins==9
- return 9
- class TickHelper:
- axis = None
- def set_axis(self, axis):
- self.axis = axis
- def create_dummy_axis(self, **kwargs):
- if self.axis is None:
- self.axis = _DummyAxis(**kwargs)
- def set_view_interval(self, vmin, vmax):
- self.axis.set_view_interval(vmin, vmax)
- def set_data_interval(self, vmin, vmax):
- self.axis.set_data_interval(vmin, vmax)
- def set_bounds(self, vmin, vmax):
- self.set_view_interval(vmin, vmax)
- self.set_data_interval(vmin, vmax)
- class Formatter(TickHelper):
- """
- Create a string based on a tick value and location.
- """
- # some classes want to see all the locs to help format
- # individual ones
- locs = []
- def __call__(self, x, pos=None):
- """
- Return the format for tick value *x* at position pos.
- ``pos=None`` indicates an unspecified location.
- """
- raise NotImplementedError('Derived must override')
- def format_ticks(self, values):
- """Return the tick labels for all the ticks at once."""
- self.set_locs(values)
- return [self(value, i) for i, value in enumerate(values)]
- def format_data(self, value):
- """
- Returns the full string representation of the value with the
- position unspecified.
- """
- return self.__call__(value)
- def format_data_short(self, value):
- """
- Return a short string version of the tick value.
- Defaults to the position-independent long value.
- """
- return self.format_data(value)
- def get_offset(self):
- return ''
- def set_locs(self, locs):
- self.locs = locs
- def fix_minus(self, s):
- """
- Some classes may want to replace a hyphen for minus with the
- proper unicode symbol (U+2212) for typographical correctness.
- The default is to not replace it.
- Note, if you use this method, e.g., in :meth:`format_data` or
- call, you probably don't want to use it for
- :meth:`format_data_short` since the toolbar uses this for
- interactive coord reporting and I doubt we can expect GUIs
- across platforms will handle the unicode correctly. So for
- now the classes that override :meth:`fix_minus` should have an
- explicit :meth:`format_data_short` method
- """
- return s
- def _set_locator(self, locator):
- """Subclasses may want to override this to set a locator."""
- pass
- class IndexFormatter(Formatter):
- """
- Format the position x to the nearest i-th label where ``i = int(x + 0.5)``.
- Positions where ``i < 0`` or ``i > len(list)`` have no tick labels.
- Parameters
- ----------
- labels : list
- List of labels.
- """
- def __init__(self, labels):
- self.labels = labels
- self.n = len(labels)
- def __call__(self, x, pos=None):
- """
- Return the format for tick value *x* at position pos.
- The position is ignored and the value is rounded to the nearest
- integer, which is used to look up the label.
- """
- i = int(x + 0.5)
- if i < 0 or i >= self.n:
- return ''
- else:
- return self.labels[i]
- class NullFormatter(Formatter):
- """
- Always return the empty string.
- """
- def __call__(self, x, pos=None):
- """
- Returns an empty string for all inputs.
- """
- return ''
- class FixedFormatter(Formatter):
- """
- Return fixed strings for tick labels based only on position, not value.
- """
- def __init__(self, seq):
- """
- Set the sequence of strings that will be used for labels.
- """
- self.seq = seq
- self.offset_string = ''
- def __call__(self, x, pos=None):
- """
- Returns the label that matches the position regardless of the
- value.
- For positions ``pos < len(seq)``, return ``seq[i]`` regardless of
- *x*. Otherwise return empty string. ``seq`` is the sequence of
- strings that this object was initialized with.
- """
- if pos is None or pos >= len(self.seq):
- return ''
- else:
- return self.seq[pos]
- def get_offset(self):
- return self.offset_string
- def set_offset_string(self, ofs):
- self.offset_string = ofs
- class FuncFormatter(Formatter):
- """
- Use a user-defined function for formatting.
- The function should take in two inputs (a tick value ``x`` and a
- position ``pos``), and return a string containing the corresponding
- tick label.
- """
- def __init__(self, func):
- self.func = func
- def __call__(self, x, pos=None):
- """
- Return the value of the user defined function.
- *x* and *pos* are passed through as-is.
- """
- return self.func(x, pos)
- class FormatStrFormatter(Formatter):
- """
- Use an old-style ('%' operator) format string to format the tick.
- The format string should have a single variable format (%) in it.
- It will be applied to the value (not the position) of the tick.
- """
- def __init__(self, fmt):
- self.fmt = fmt
- def __call__(self, x, pos=None):
- """
- Return the formatted label string.
- Only the value *x* is formatted. The position is ignored.
- """
- return self.fmt % x
- class StrMethodFormatter(Formatter):
- """
- Use a new-style format string (as used by `str.format()`)
- to format the tick.
- The field used for the value must be labeled *x* and the field used
- for the position must be labeled *pos*.
- """
- def __init__(self, fmt):
- self.fmt = fmt
- def __call__(self, x, pos=None):
- """
- Return the formatted label string.
- *x* and *pos* are passed to `str.format` as keyword arguments
- with those exact names.
- """
- return self.fmt.format(x=x, pos=pos)
- class OldScalarFormatter(Formatter):
- """
- Tick location is a plain old number.
- """
- def __call__(self, x, pos=None):
- """
- Return the format for tick val *x* based on the width of the axis.
- The position *pos* is ignored.
- """
- xmin, xmax = self.axis.get_view_interval()
- # If the number is not too big and it's an int, format it as an int.
- if abs(x) < 1e4 and x == int(x):
- return '%d' % x
- d = abs(xmax - xmin)
- fmt = ('%1.3e' if d < 1e-2 else
- '%1.3f' if d <= 1 else
- '%1.2f' if d <= 10 else
- '%1.1f' if d <= 1e5 else
- '%1.1e')
- s = fmt % x
- tup = s.split('e')
- if len(tup) == 2:
- mantissa = tup[0].rstrip('0').rstrip('.')
- sign = tup[1][0].replace('+', '')
- exponent = tup[1][1:].lstrip('0')
- s = '%se%s%s' % (mantissa, sign, exponent)
- else:
- s = s.rstrip('0').rstrip('.')
- return s
- @cbook.deprecated("3.1")
- def pprint_val(self, x, d):
- """
- Formats the value *x* based on the size of the axis range *d*.
- """
- # If the number is not too big and it's an int, format it as an int.
- if abs(x) < 1e4 and x == int(x):
- return '%d' % x
- if d < 1e-2:
- fmt = '%1.3e'
- elif d < 1e-1:
- fmt = '%1.3f'
- elif d > 1e5:
- fmt = '%1.1e'
- elif d > 10:
- fmt = '%1.1f'
- elif d > 1:
- fmt = '%1.2f'
- else:
- fmt = '%1.3f'
- s = fmt % x
- tup = s.split('e')
- if len(tup) == 2:
- mantissa = tup[0].rstrip('0').rstrip('.')
- sign = tup[1][0].replace('+', '')
- exponent = tup[1][1:].lstrip('0')
- s = '%se%s%s' % (mantissa, sign, exponent)
- else:
- s = s.rstrip('0').rstrip('.')
- return s
- class ScalarFormatter(Formatter):
- """
- Format tick values as a number.
- Tick value is interpreted as a plain old number. If
- ``useOffset==True`` and the data range is much smaller than the data
- average, then an offset will be determined such that the tick labels
- are meaningful. Scientific notation is used for ``data < 10^-n`` or
- ``data >= 10^m``, where ``n`` and ``m`` are the power limits set
- using ``set_powerlimits((n, m))``. The defaults for these are
- controlled by the ``axes.formatter.limits`` rc parameter.
- """
- def __init__(self, useOffset=None, useMathText=None, useLocale=None):
- # useOffset allows plotting small data ranges with large offsets: for
- # example: [1+1e-9, 1+2e-9, 1+3e-9] useMathText will render the offset
- # and scientific notation in mathtext
- if useOffset is None:
- useOffset = rcParams['axes.formatter.useoffset']
- self._offset_threshold = rcParams['axes.formatter.offset_threshold']
- self.set_useOffset(useOffset)
- self._usetex = rcParams['text.usetex']
- if useMathText is None:
- useMathText = rcParams['axes.formatter.use_mathtext']
- self.set_useMathText(useMathText)
- self.orderOfMagnitude = 0
- self.format = ''
- self._scientific = True
- self._powerlimits = rcParams['axes.formatter.limits']
- if useLocale is None:
- useLocale = rcParams['axes.formatter.use_locale']
- self._useLocale = useLocale
- def get_useOffset(self):
- return self._useOffset
- def set_useOffset(self, val):
- if val in [True, False]:
- self.offset = 0
- self._useOffset = val
- else:
- self._useOffset = False
- self.offset = val
- useOffset = property(fget=get_useOffset, fset=set_useOffset)
- def get_useLocale(self):
- return self._useLocale
- def set_useLocale(self, val):
- if val is None:
- self._useLocale = rcParams['axes.formatter.use_locale']
- else:
- self._useLocale = val
- useLocale = property(fget=get_useLocale, fset=set_useLocale)
- def get_useMathText(self):
- return self._useMathText
- def set_useMathText(self, val):
- if val is None:
- self._useMathText = rcParams['axes.formatter.use_mathtext']
- else:
- self._useMathText = val
- useMathText = property(fget=get_useMathText, fset=set_useMathText)
- def fix_minus(self, s):
- """
- Replace hyphens with a unicode minus.
- """
- if rcParams['text.usetex'] or not rcParams['axes.unicode_minus']:
- return s
- else:
- return s.replace('-', '\N{MINUS SIGN}')
- def __call__(self, x, pos=None):
- """
- Return the format for tick value *x* at position *pos*.
- """
- if len(self.locs) == 0:
- return ''
- else:
- xp = (x - self.offset) / (10. ** self.orderOfMagnitude)
- if np.abs(xp) < 1e-8:
- xp = 0
- if self._useLocale:
- s = locale.format_string(self.format, (xp,))
- else:
- s = self.format % xp
- return self.fix_minus(s)
- def set_scientific(self, b):
- """
- Turn scientific notation on or off.
- See Also
- --------
- ScalarFormatter.set_powerlimits
- """
- self._scientific = bool(b)
- def set_powerlimits(self, lims):
- """
- Sets size thresholds for scientific notation.
- Parameters
- ----------
- lims : (min_exp, max_exp)
- A tuple containing the powers of 10 that determine the switchover
- threshold. Numbers below ``10**min_exp`` and above ``10**max_exp``
- will be displayed in scientific notation.
- For example, ``formatter.set_powerlimits((-3, 4))`` sets the
- pre-2007 default in which scientific notation is used for
- numbers less than 1e-3 or greater than 1e4.
- See Also
- --------
- ScalarFormatter.set_scientific
- """
- if len(lims) != 2:
- raise ValueError("'lims' must be a sequence of length 2")
- self._powerlimits = lims
- def format_data_short(self, value):
- """
- Return a short formatted string representation of a number.
- """
- if self._useLocale:
- return locale.format_string('%-12g', (value,))
- elif isinstance(value, np.ma.MaskedArray) and value.mask:
- return ''
- else:
- return '%-12g' % value
- def format_data(self, value):
- """
- Return a formatted string representation of a number.
- """
- if self._useLocale:
- s = locale.format_string('%1.10e', (value,))
- else:
- s = '%1.10e' % value
- s = self._formatSciNotation(s)
- return self.fix_minus(s)
- def get_offset(self):
- """
- Return scientific notation, plus offset.
- """
- if len(self.locs) == 0:
- return ''
- s = ''
- if self.orderOfMagnitude or self.offset:
- offsetStr = ''
- sciNotStr = ''
- if self.offset:
- offsetStr = self.format_data(self.offset)
- if self.offset > 0:
- offsetStr = '+' + offsetStr
- if self.orderOfMagnitude:
- if self._usetex or self._useMathText:
- sciNotStr = self.format_data(10 ** self.orderOfMagnitude)
- else:
- sciNotStr = '1e%d' % self.orderOfMagnitude
- if self._useMathText:
- if sciNotStr != '':
- sciNotStr = r'\times%s' % _mathdefault(sciNotStr)
- s = ''.join(('$', sciNotStr, _mathdefault(offsetStr), '$'))
- elif self._usetex:
- if sciNotStr != '':
- sciNotStr = r'\times%s' % sciNotStr
- s = ''.join(('$', sciNotStr, offsetStr, '$'))
- else:
- s = ''.join((sciNotStr, offsetStr))
- return self.fix_minus(s)
- def set_locs(self, locs):
- """
- Set the locations of the ticks.
- """
- self.locs = locs
- if len(self.locs) > 0:
- if self._useOffset:
- self._compute_offset()
- self._set_order_of_magnitude()
- self._set_format()
- def _compute_offset(self):
- locs = self.locs
- # Restrict to visible ticks.
- vmin, vmax = sorted(self.axis.get_view_interval())
- locs = np.asarray(locs)
- locs = locs[(vmin <= locs) & (locs <= vmax)]
- if not len(locs):
- self.offset = 0
- return
- lmin, lmax = locs.min(), locs.max()
- # Only use offset if there are at least two ticks and every tick has
- # the same sign.
- if lmin == lmax or lmin <= 0 <= lmax:
- self.offset = 0
- return
- # min, max comparing absolute values (we want division to round towards
- # zero so we work on absolute values).
- abs_min, abs_max = sorted([abs(float(lmin)), abs(float(lmax))])
- sign = math.copysign(1, lmin)
- # What is the smallest power of ten such that abs_min and abs_max are
- # equal up to that precision?
- # Note: Internally using oom instead of 10 ** oom avoids some numerical
- # accuracy issues.
- oom_max = np.ceil(math.log10(abs_max))
- oom = 1 + next(oom for oom in itertools.count(oom_max, -1)
- if abs_min // 10 ** oom != abs_max // 10 ** oom)
- if (abs_max - abs_min) / 10 ** oom <= 1e-2:
- # Handle the case of straddling a multiple of a large power of ten
- # (relative to the span).
- # What is the smallest power of ten such that abs_min and abs_max
- # are no more than 1 apart at that precision?
- oom = 1 + next(oom for oom in itertools.count(oom_max, -1)
- if abs_max // 10 ** oom - abs_min // 10 ** oom > 1)
- # Only use offset if it saves at least _offset_threshold digits.
- n = self._offset_threshold - 1
- self.offset = (sign * (abs_max // 10 ** oom) * 10 ** oom
- if abs_max // 10 ** oom >= 10**n
- else 0)
- def _set_order_of_magnitude(self):
- # if scientific notation is to be used, find the appropriate exponent
- # if using an numerical offset, find the exponent after applying the
- # offset. When lower power limit = upper <> 0, use provided exponent.
- if not self._scientific:
- self.orderOfMagnitude = 0
- return
- if self._powerlimits[0] == self._powerlimits[1] != 0:
- # fixed scaling when lower power limit = upper <> 0.
- self.orderOfMagnitude = self._powerlimits[0]
- return
- # restrict to visible ticks
- vmin, vmax = sorted(self.axis.get_view_interval())
- locs = np.asarray(self.locs)
- locs = locs[(vmin <= locs) & (locs <= vmax)]
- locs = np.abs(locs)
- if not len(locs):
- self.orderOfMagnitude = 0
- return
- if self.offset:
- oom = math.floor(math.log10(vmax - vmin))
- else:
- if locs[0] > locs[-1]:
- val = locs[0]
- else:
- val = locs[-1]
- if val == 0:
- oom = 0
- else:
- oom = math.floor(math.log10(val))
- if oom <= self._powerlimits[0]:
- self.orderOfMagnitude = oom
- elif oom >= self._powerlimits[1]:
- self.orderOfMagnitude = oom
- else:
- self.orderOfMagnitude = 0
- def _set_format(self):
- # set the format string to format all the ticklabels
- if len(self.locs) < 2:
- # Temporarily augment the locations with the axis end points.
- _locs = [*self.locs, *self.axis.get_view_interval()]
- else:
- _locs = self.locs
- locs = (np.asarray(_locs) - self.offset) / 10. ** self.orderOfMagnitude
- loc_range = np.ptp(locs)
- # Curvilinear coordinates can yield two identical points.
- if loc_range == 0:
- loc_range = np.max(np.abs(locs))
- # Both points might be zero.
- if loc_range == 0:
- loc_range = 1
- if len(self.locs) < 2:
- # We needed the end points only for the loc_range calculation.
- locs = locs[:-2]
- loc_range_oom = int(math.floor(math.log10(loc_range)))
- # first estimate:
- sigfigs = max(0, 3 - loc_range_oom)
- # refined estimate:
- thresh = 1e-3 * 10 ** loc_range_oom
- while sigfigs >= 0:
- if np.abs(locs - np.round(locs, decimals=sigfigs)).max() < thresh:
- sigfigs -= 1
- else:
- break
- sigfigs += 1
- self.format = '%1.' + str(sigfigs) + 'f'
- if self._usetex:
- self.format = '$%s$' % self.format
- elif self._useMathText:
- self.format = '$%s$' % _mathdefault(self.format)
- @cbook.deprecated("3.1")
- def pprint_val(self, x):
- xp = (x - self.offset) / (10. ** self.orderOfMagnitude)
- if np.abs(xp) < 1e-8:
- xp = 0
- if self._useLocale:
- return locale.format_string(self.format, (xp,))
- else:
- return self.format % xp
- def _formatSciNotation(self, s):
- # transform 1e+004 into 1e4, for example
- if self._useLocale:
- decimal_point = locale.localeconv()['decimal_point']
- positive_sign = locale.localeconv()['positive_sign']
- else:
- decimal_point = '.'
- positive_sign = '+'
- tup = s.split('e')
- try:
- significand = tup[0].rstrip('0').rstrip(decimal_point)
- sign = tup[1][0].replace(positive_sign, '')
- exponent = tup[1][1:].lstrip('0')
- if self._useMathText or self._usetex:
- if significand == '1' and exponent != '':
- # reformat 1x10^y as 10^y
- significand = ''
- if exponent:
- exponent = '10^{%s%s}' % (sign, exponent)
- if significand and exponent:
- return r'%s{\times}%s' % (significand, exponent)
- else:
- return r'%s%s' % (significand, exponent)
- else:
- s = ('%se%s%s' % (significand, sign, exponent)).rstrip('e')
- return s
- except IndexError:
- return s
- class LogFormatter(Formatter):
- """
- Base class for formatting ticks on a log or symlog scale.
- It may be instantiated directly, or subclassed.
- Parameters
- ----------
- base : float, optional, default: 10.
- Base of the logarithm used in all calculations.
- labelOnlyBase : bool, optional, default: False
- If True, label ticks only at integer powers of base.
- This is normally True for major ticks and False for
- minor ticks.
- minor_thresholds : (subset, all), optional, default: (1, 0.4)
- If labelOnlyBase is False, these two numbers control
- the labeling of ticks that are not at integer powers of
- base; normally these are the minor ticks. The controlling
- parameter is the log of the axis data range. In the typical
- case where base is 10 it is the number of decades spanned
- by the axis, so we can call it 'numdec'. If ``numdec <= all``,
- all minor ticks will be labeled. If ``all < numdec <= subset``,
- then only a subset of minor ticks will be labeled, so as to
- avoid crowding. If ``numdec > subset`` then no minor ticks will
- be labeled.
- linthresh : None or float, optional, default: None
- If a symmetric log scale is in use, its ``linthresh``
- parameter must be supplied here.
- Notes
- -----
- The `set_locs` method must be called to enable the subsetting
- logic controlled by the ``minor_thresholds`` parameter.
- In some cases such as the colorbar, there is no distinction between
- major and minor ticks; the tick locations might be set manually,
- or by a locator that puts ticks at integer powers of base and
- at intermediate locations. For this situation, disable the
- minor_thresholds logic by using ``minor_thresholds=(np.inf, np.inf)``,
- so that all ticks will be labeled.
- To disable labeling of minor ticks when 'labelOnlyBase' is False,
- use ``minor_thresholds=(0, 0)``. This is the default for the
- "classic" style.
- Examples
- --------
- To label a subset of minor ticks when the view limits span up
- to 2 decades, and all of the ticks when zoomed in to 0.5 decades
- or less, use ``minor_thresholds=(2, 0.5)``.
- To label all minor ticks when the view limits span up to 1.5
- decades, use ``minor_thresholds=(1.5, 1.5)``.
- """
- def __init__(self, base=10.0, labelOnlyBase=False,
- minor_thresholds=None,
- linthresh=None):
- self._base = float(base)
- self.labelOnlyBase = labelOnlyBase
- if minor_thresholds is None:
- if rcParams['_internal.classic_mode']:
- minor_thresholds = (0, 0)
- else:
- minor_thresholds = (1, 0.4)
- self.minor_thresholds = minor_thresholds
- self._sublabels = None
- self._linthresh = linthresh
- def base(self, base):
- """
- Change the *base* for labeling.
- .. warning::
- Should always match the base used for :class:`LogLocator`
- """
- self._base = base
- def label_minor(self, labelOnlyBase):
- """
- Switch minor tick labeling on or off.
- Parameters
- ----------
- labelOnlyBase : bool
- If True, label ticks only at integer powers of base.
- """
- self.labelOnlyBase = labelOnlyBase
- def set_locs(self, locs=None):
- """
- Use axis view limits to control which ticks are labeled.
- The *locs* parameter is ignored in the present algorithm.
- """
- if np.isinf(self.minor_thresholds[0]):
- self._sublabels = None
- return
- # Handle symlog case:
- linthresh = self._linthresh
- if linthresh is None:
- try:
- linthresh = self.axis.get_transform().linthresh
- except AttributeError:
- pass
- vmin, vmax = self.axis.get_view_interval()
- if vmin > vmax:
- vmin, vmax = vmax, vmin
- if linthresh is None and vmin <= 0:
- # It's probably a colorbar with
- # a format kwarg setting a LogFormatter in the manner
- # that worked with 1.5.x, but that doesn't work now.
- self._sublabels = {1} # label powers of base
- return
- b = self._base
- if linthresh is not None: # symlog
- # Only compute the number of decades in the logarithmic part of the
- # axis
- numdec = 0
- if vmin < -linthresh:
- rhs = min(vmax, -linthresh)
- numdec += math.log(vmin / rhs) / math.log(b)
- if vmax > linthresh:
- lhs = max(vmin, linthresh)
- numdec += math.log(vmax / lhs) / math.log(b)
- else:
- vmin = math.log(vmin) / math.log(b)
- vmax = math.log(vmax) / math.log(b)
- numdec = abs(vmax - vmin)
- if numdec > self.minor_thresholds[0]:
- # Label only bases
- self._sublabels = {1}
- elif numdec > self.minor_thresholds[1]:
- # Add labels between bases at log-spaced coefficients;
- # include base powers in case the locations include
- # "major" and "minor" points, as in colorbar.
- c = np.logspace(0, 1, int(b)//2 + 1, base=b)
- self._sublabels = set(np.round(c))
- # For base 10, this yields (1, 2, 3, 4, 6, 10).
- else:
- # Label all integer multiples of base**n.
- self._sublabels = set(np.arange(1, b + 1))
- def _num_to_string(self, x, vmin, vmax):
- if x > 10000:
- s = '%1.0e' % x
- elif x < 1:
- s = '%1.0e' % x
- else:
- s = self._pprint_val(x, vmax - vmin)
- return s
- def __call__(self, x, pos=None):
- """
- Return the format for tick val *x*.
- """
- if x == 0.0: # Symlog
- return '0'
- x = abs(x)
- b = self._base
- # only label the decades
- fx = math.log(x) / math.log(b)
- is_x_decade = is_close_to_int(fx)
- exponent = round(fx) if is_x_decade else np.floor(fx)
- coeff = round(x / b ** exponent)
- if self.labelOnlyBase and not is_x_decade:
- return ''
- if self._sublabels is not None and coeff not in self._sublabels:
- return ''
- vmin, vmax = self.axis.get_view_interval()
- vmin, vmax = mtransforms.nonsingular(vmin, vmax, expander=0.05)
- s = self._num_to_string(x, vmin, vmax)
- return self.fix_minus(s)
- def format_data(self, value):
- b = self.labelOnlyBase
- self.labelOnlyBase = False
- value = cbook.strip_math(self.__call__(value))
- self.labelOnlyBase = b
- return value
- def format_data_short(self, value):
- """
- Return a short formatted string representation of a number.
- """
- return '%-12g' % value
- @cbook.deprecated("3.1")
- def pprint_val(self, *args, **kwargs):
- return self._pprint_val(*args, **kwargs)
- def _pprint_val(self, x, d):
- # If the number is not too big and it's an int, format it as an int.
- if abs(x) < 1e4 and x == int(x):
- return '%d' % x
- fmt = ('%1.3e' if d < 1e-2 else
- '%1.3f' if d <= 1 else
- '%1.2f' if d <= 10 else
- '%1.1f' if d <= 1e5 else
- '%1.1e')
- s = fmt % x
- tup = s.split('e')
- if len(tup) == 2:
- mantissa = tup[0].rstrip('0').rstrip('.')
- exponent = int(tup[1])
- if exponent:
- s = '%se%d' % (mantissa, exponent)
- else:
- s = mantissa
- else:
- s = s.rstrip('0').rstrip('.')
- return s
- class LogFormatterExponent(LogFormatter):
- """
- Format values for log axis using ``exponent = log_base(value)``.
- """
- def _num_to_string(self, x, vmin, vmax):
- fx = math.log(x) / math.log(self._base)
- if abs(fx) > 10000:
- s = '%1.0g' % fx
- elif abs(fx) < 1:
- s = '%1.0g' % fx
- else:
- fd = math.log(vmax - vmin) / math.log(self._base)
- s = self._pprint_val(fx, fd)
- return s
- class LogFormatterMathtext(LogFormatter):
- """
- Format values for log axis using ``exponent = log_base(value)``.
- """
- def _non_decade_format(self, sign_string, base, fx, usetex):
- 'Return string for non-decade locations'
- if usetex:
- return (r'$%s%s^{%.2f}$') % (sign_string, base, fx)
- else:
- return ('$%s$' % _mathdefault('%s%s^{%.2f}' %
- (sign_string, base, fx)))
- def __call__(self, x, pos=None):
- """
- Return the format for tick value *x*.
- The position *pos* is ignored.
- """
- usetex = rcParams['text.usetex']
- min_exp = rcParams['axes.formatter.min_exponent']
- if x == 0: # Symlog
- if usetex:
- return '$0$'
- else:
- return '$%s$' % _mathdefault('0')
- sign_string = '-' if x < 0 else ''
- x = abs(x)
- b = self._base
- # only label the decades
- fx = math.log(x) / math.log(b)
- is_x_decade = is_close_to_int(fx)
- exponent = round(fx) if is_x_decade else np.floor(fx)
- coeff = round(x / b ** exponent)
- if is_x_decade:
- fx = round(fx)
- if self.labelOnlyBase and not is_x_decade:
- return ''
- if self._sublabels is not None and coeff not in self._sublabels:
- return ''
- # use string formatting of the base if it is not an integer
- if b % 1 == 0.0:
- base = '%d' % b
- else:
- base = '%s' % b
- if np.abs(fx) < min_exp:
- if usetex:
- return r'${0}{1:g}$'.format(sign_string, x)
- else:
- return '${0}$'.format(_mathdefault(
- '{0}{1:g}'.format(sign_string, x)))
- elif not is_x_decade:
- return self._non_decade_format(sign_string, base, fx, usetex)
- elif usetex:
- return r'$%s%s^{%d}$' % (sign_string, base, fx)
- else:
- return '$%s$' % _mathdefault('%s%s^{%d}' % (sign_string, base, fx))
- class LogFormatterSciNotation(LogFormatterMathtext):
- """
- Format values following scientific notation in a logarithmic axis.
- """
- def _non_decade_format(self, sign_string, base, fx, usetex):
- 'Return string for non-decade locations'
- b = float(base)
- exponent = math.floor(fx)
- coeff = b ** fx / b ** exponent
- if is_close_to_int(coeff):
- coeff = round(coeff)
- if usetex:
- return (r'$%s%g\times%s^{%d}$') % \
- (sign_string, coeff, base, exponent)
- else:
- return ('$%s$' % _mathdefault(r'%s%g\times%s^{%d}' %
- (sign_string, coeff, base, exponent)))
- class LogitFormatter(Formatter):
- """
- Probability formatter (using Math text).
- """
- def __init__(
- self,
- *,
- use_overline=False,
- one_half=r"\frac{1}{2}",
- minor=False,
- minor_threshold=25,
- minor_number=6,
- ):
- r"""
- Parameters
- ----------
- use_overline : bool, default: False
- If x > 1/2, with x = 1-v, indicate if x should be displayed as
- $\overline{v}$. The default is to display $1-v$.
- one_half : str, default: r"\frac{1}{2}"
- The string used to represent 1/2.
- minor : bool, default: False
- Indicate if the formatter is formatting minor ticks or not.
- Basically minor ticks are not labelled, except when only few ticks
- are provided, ticks with most space with neighbor ticks are
- labelled. See other parameters to change the default behavior.
- minor_threshold : int, default: 25
- Maximum number of locs for labelling some minor ticks. This
- parameter have no effect if minor is False.
- minor_number : int, default: 6
- Number of ticks which are labelled when the number of ticks is
- below the threshold.
- """
- self._use_overline = use_overline
- self._one_half = one_half
- self._minor = minor
- self._labelled = set()
- self._minor_threshold = minor_threshold
- self._minor_number = minor_number
- def use_overline(self, use_overline):
- r"""
- Switch display mode with overline for labelling p>1/2.
- Parameters
- ----------
- use_overline : bool, default: False
- If x > 1/2, with x = 1-v, indicate if x should be displayed as
- $\overline{v}$. The default is to display $1-v$.
- """
- self._use_overline = use_overline
- def set_one_half(self, one_half):
- r"""
- Set the way one half is displayed.
- one_half : str, default: r"\frac{1}{2}"
- The string used to represent 1/2.
- """
- self._one_half = one_half
- def set_minor_threshold(self, minor_threshold):
- """
- Set the threshold for labelling minors ticks.
- Parameters
- ----------
- minor_threshold : int
- Maximum number of locations for labelling some minor ticks. This
- parameter have no effect if minor is False.
- """
- self._minor_threshold = minor_threshold
- def set_minor_number(self, minor_number):
- """
- Set the number of minor ticks to label when some minor ticks are
- labelled.
- Parameters
- ----------
- minor_number : int
- Number of ticks which are labelled when the number of ticks is
- below the threshold.
- """
- self._minor_number = minor_number
- def set_locs(self, locs):
- self.locs = np.array(locs)
- self._labelled.clear()
- if not self._minor:
- return None
- if all(
- is_decade(x, rtol=1e-7)
- or is_decade(1 - x, rtol=1e-7)
- or (is_close_to_int(2 * x) and int(np.round(2 * x)) == 1)
- for x in locs
- ):
- # minor ticks are subsample from ideal, so no label
- return None
- if len(locs) < self._minor_threshold:
- if len(locs) < self._minor_number:
- self._labelled.update(locs)
- else:
- # we do not have a lot of minor ticks, so only few decades are
- # displayed, then we choose some (spaced) minor ticks to label.
- # Only minor ticks are known, we assume it is sufficient to
- # choice which ticks are displayed.
- # For each ticks we compute the distance between the ticks and
- # the previous, and between the ticks and the next one. Ticks
- # with smallest minimum are chosen. As tiebreak, the ticks
- # with smallest sum is chosen.
- diff = np.diff(-np.log(1 / self.locs - 1))
- space_pessimistic = np.minimum(
- np.concatenate(((np.inf,), diff)),
- np.concatenate((diff, (np.inf,))),
- )
- space_sum = (
- np.concatenate(((0,), diff))
- + np.concatenate((diff, (0,)))
- )
- good_minor = sorted(
- range(len(self.locs)),
- key=lambda i: (space_pessimistic[i], space_sum[i]),
- )[-self._minor_number:]
- self._labelled.update(locs[i] for i in good_minor)
- def _format_value(self, x, locs, sci_notation=True):
- if sci_notation:
- exponent = math.floor(np.log10(x))
- min_precision = 0
- else:
- exponent = 0
- min_precision = 1
- value = x * 10 ** (-exponent)
- if len(locs) < 2:
- precision = min_precision
- else:
- diff = np.sort(np.abs(locs - x))[1]
- precision = -np.log10(diff) + exponent
- precision = (
- int(np.round(precision))
- if is_close_to_int(precision)
- else math.ceil(precision)
- )
- if precision < min_precision:
- precision = min_precision
- mantissa = r"%.*f" % (precision, value)
- if not sci_notation:
- return mantissa
- s = r"%s\cdot10^{%d}" % (mantissa, exponent)
- return s
- def _one_minus(self, s):
- if self._use_overline:
- return r"\overline{%s}" % s
- else:
- return "1-{}".format(s)
- def __call__(self, x, pos=None):
- if self._minor and x not in self._labelled:
- return ""
- if x <= 0 or x >= 1:
- return ""
- usetex = rcParams["text.usetex"]
- if is_close_to_int(2 * x) and round(2 * x) == 1:
- s = self._one_half
- elif x < 0.5 and is_decade(x, rtol=1e-7):
- exponent = round(np.log10(x))
- s = "10^{%d}" % exponent
- elif x > 0.5 and is_decade(1 - x, rtol=1e-7):
- exponent = round(np.log10(1 - x))
- s = self._one_minus("10^{%d}" % exponent)
- elif x < 0.1:
- s = self._format_value(x, self.locs)
- elif x > 0.9:
- s = self._one_minus(self._format_value(1-x, 1-self.locs))
- else:
- s = self._format_value(x, self.locs, sci_notation=False)
- if usetex:
- return "$%s$" % s
- return "$%s$" % _mathdefault(s)
- def format_data_short(self, value):
- """
- Return a short formatted string representation of a number.
- """
- # thresholds choosen for use scienfic notation if and only if exponent
- # is less or equal than -2.
- if value < 0.1:
- return "{:e}".format(value)
- if value < 0.9:
- return "{:f}".format(value)
- return "1-{:e}".format(1 - value)
- class EngFormatter(Formatter):
- """
- Formats axis values using engineering prefixes to represent powers
- of 1000, plus a specified unit, e.g., 10 MHz instead of 1e7.
- """
- # The SI engineering prefixes
- ENG_PREFIXES = {
- -24: "y",
- -21: "z",
- -18: "a",
- -15: "f",
- -12: "p",
- -9: "n",
- -6: "\N{MICRO SIGN}",
- -3: "m",
- 0: "",
- 3: "k",
- 6: "M",
- 9: "G",
- 12: "T",
- 15: "P",
- 18: "E",
- 21: "Z",
- 24: "Y"
- }
- def __init__(self, unit="", places=None, sep=" ", *, usetex=None,
- useMathText=None):
- r"""
- Parameters
- ----------
- unit : str (default: "")
- Unit symbol to use, suitable for use with single-letter
- representations of powers of 1000. For example, 'Hz' or 'm'.
- places : int (default: None)
- Precision with which to display the number, specified in
- digits after the decimal point (there will be between one
- and three digits before the decimal point). If it is None,
- the formatting falls back to the floating point format '%g',
- which displays up to 6 *significant* digits, i.e. the equivalent
- value for *places* varies between 0 and 5 (inclusive).
- sep : str (default: " ")
- Separator used between the value and the prefix/unit. For
- example, one get '3.14 mV' if ``sep`` is " " (default) and
- '3.14mV' if ``sep`` is "". Besides the default behavior, some
- other useful options may be:
- * ``sep=""`` to append directly the prefix/unit to the value;
- * ``sep="\N{THIN SPACE}"`` (``U+2009``);
- * ``sep="\N{NARROW NO-BREAK SPACE}"`` (``U+202F``);
- * ``sep="\N{NO-BREAK SPACE}"`` (``U+00A0``).
- usetex : bool (default: None)
- To enable/disable the use of TeX's math mode for rendering the
- numbers in the formatter.
- useMathText : bool (default: None)
- To enable/disable the use mathtext for rendering the numbers in
- the formatter.
- """
- self.unit = unit
- self.places = places
- self.sep = sep
- self.set_usetex(usetex)
- self.set_useMathText(useMathText)
- def get_usetex(self):
- return self._usetex
- def set_usetex(self, val):
- if val is None:
- self._usetex = rcParams['text.usetex']
- else:
- self._usetex = val
- usetex = property(fget=get_usetex, fset=set_usetex)
- def get_useMathText(self):
- return self._useMathText
- def set_useMathText(self, val):
- if val is None:
- self._useMathText = rcParams['axes.formatter.use_mathtext']
- else:
- self._useMathText = val
- useMathText = property(fget=get_useMathText, fset=set_useMathText)
- def fix_minus(self, s):
- """
- Replace hyphens with a unicode minus.
- """
- return ScalarFormatter.fix_minus(self, s)
- def __call__(self, x, pos=None):
- s = "%s%s" % (self.format_eng(x), self.unit)
- # Remove the trailing separator when there is neither prefix nor unit
- if self.sep and s.endswith(self.sep):
- s = s[:-len(self.sep)]
- return self.fix_minus(s)
- def format_eng(self, num):
- """
- Formats a number in engineering notation, appending a letter
- representing the power of 1000 of the original number.
- Some examples:
- >>> format_eng(0) # for self.places = 0
- '0'
- >>> format_eng(1000000) # for self.places = 1
- '1.0 M'
- >>> format_eng("-1e-6") # for self.places = 2
- '-1.00 \N{MICRO SIGN}'
- """
- sign = 1
- fmt = "g" if self.places is None else ".{:d}f".format(self.places)
- if num < 0:
- sign = -1
- num = -num
- if num != 0:
- pow10 = int(math.floor(math.log10(num) / 3) * 3)
- else:
- pow10 = 0
- # Force num to zero, to avoid inconsistencies like
- # format_eng(-0) = "0" and format_eng(0.0) = "0"
- # but format_eng(-0.0) = "-0.0"
- num = 0.0
- pow10 = np.clip(pow10, min(self.ENG_PREFIXES), max(self.ENG_PREFIXES))
- mant = sign * num / (10.0 ** pow10)
- # Taking care of the cases like 999.9..., which may be rounded to 1000
- # instead of 1 k. Beware of the corner case of values that are beyond
- # the range of SI prefixes (i.e. > 'Y').
- if (abs(float(format(mant, fmt))) >= 1000
- and pow10 < max(self.ENG_PREFIXES)):
- mant /= 1000
- pow10 += 3
- prefix = self.ENG_PREFIXES[int(pow10)]
- if self._usetex or self._useMathText:
- formatted = "${mant:{fmt}}${sep}{prefix}".format(
- mant=mant, sep=self.sep, prefix=prefix, fmt=fmt)
- else:
- formatted = "{mant:{fmt}}{sep}{prefix}".format(
- mant=mant, sep=self.sep, prefix=prefix, fmt=fmt)
- return formatted
- class PercentFormatter(Formatter):
- """
- Format numbers as a percentage.
- Parameters
- ----------
- xmax : float
- Determines how the number is converted into a percentage.
- *xmax* is the data value that corresponds to 100%.
- Percentages are computed as ``x / xmax * 100``. So if the data is
- already scaled to be percentages, *xmax* will be 100. Another common
- situation is where *xmax* is 1.0.
- decimals : None or int
- The number of decimal places to place after the point.
- If *None* (the default), the number will be computed automatically.
- symbol : str or None
- A string that will be appended to the label. It may be
- *None* or empty to indicate that no symbol should be used. LaTeX
- special characters are escaped in *symbol* whenever latex mode is
- enabled, unless *is_latex* is *True*.
- is_latex : bool
- If *False*, reserved LaTeX characters in *symbol* will be escaped.
- """
- def __init__(self, xmax=100, decimals=None, symbol='%', is_latex=False):
- self.xmax = xmax + 0.0
- self.decimals = decimals
- self._symbol = symbol
- self._is_latex = is_latex
- def __call__(self, x, pos=None):
- """
- Formats the tick as a percentage with the appropriate scaling.
- """
- ax_min, ax_max = self.axis.get_view_interval()
- display_range = abs(ax_max - ax_min)
- return self.fix_minus(self.format_pct(x, display_range))
- def format_pct(self, x, display_range):
- """
- Formats the number as a percentage number with the correct
- number of decimals and adds the percent symbol, if any.
- If `self.decimals` is `None`, the number of digits after the
- decimal point is set based on the `display_range` of the axis
- as follows:
- +---------------+----------+------------------------+
- | display_range | decimals | sample |
- +---------------+----------+------------------------+
- | >50 | 0 | ``x = 34.5`` => 35% |
- +---------------+----------+------------------------+
- | >5 | 1 | ``x = 34.5`` => 34.5% |
- +---------------+----------+------------------------+
- | >0.5 | 2 | ``x = 34.5`` => 34.50% |
- +---------------+----------+------------------------+
- | ... | ... | ... |
- +---------------+----------+------------------------+
- This method will not be very good for tiny axis ranges or
- extremely large ones. It assumes that the values on the chart
- are percentages displayed on a reasonable scale.
- """
- x = self.convert_to_pct(x)
- if self.decimals is None:
- # conversion works because display_range is a difference
- scaled_range = self.convert_to_pct(display_range)
- if scaled_range <= 0:
- decimals = 0
- else:
- # Luckily Python's built-in ceil rounds to +inf, not away from
- # zero. This is very important since the equation for decimals
- # starts out as `scaled_range > 0.5 * 10**(2 - decimals)`
- # and ends up with `decimals > 2 - log10(2 * scaled_range)`.
- decimals = math.ceil(2.0 - math.log10(2.0 * scaled_range))
- if decimals > 5:
- decimals = 5
- elif decimals < 0:
- decimals = 0
- else:
- decimals = self.decimals
- s = '{x:0.{decimals}f}'.format(x=x, decimals=int(decimals))
- return s + self.symbol
- def convert_to_pct(self, x):
- return 100.0 * (x / self.xmax)
- @property
- def symbol(self):
- r"""
- The configured percent symbol as a string.
- If LaTeX is enabled via :rc:`text.usetex`, the special characters
- ``{'#', '$', '%', '&', '~', '_', '^', '\', '{', '}'}`` are
- automatically escaped in the string.
- """
- symbol = self._symbol
- if not symbol:
- symbol = ''
- elif rcParams['text.usetex'] and not self._is_latex:
- # Source: http://www.personal.ceu.hu/tex/specchar.htm
- # Backslash must be first for this to work correctly since
- # it keeps getting added in
- for spec in r'\#$%&~_^{}':
- symbol = symbol.replace(spec, '\\' + spec)
- return symbol
- @symbol.setter
- def symbol(self, symbol):
- self._symbol = symbol
- class Locator(TickHelper):
- """
- Determine the tick locations;
- Note that the same locator should not be used across multiple
- `~matplotlib.axis.Axis` because the locator stores references to the Axis
- data and view limits.
- """
- # Some automatic tick locators can generate so many ticks they
- # kill the machine when you try and render them.
- # This parameter is set to cause locators to raise an error if too
- # many ticks are generated.
- MAXTICKS = 1000
- def tick_values(self, vmin, vmax):
- """
- Return the values of the located ticks given **vmin** and **vmax**.
- .. note::
- To get tick locations with the vmin and vmax values defined
- automatically for the associated :attr:`axis` simply call
- the Locator instance::
- >>> print(type(loc))
- <type 'Locator'>
- >>> print(loc())
- [1, 2, 3, 4]
- """
- raise NotImplementedError('Derived must override')
- def set_params(self, **kwargs):
- """
- Do nothing, and raise a warning. Any locator class not supporting the
- set_params() function will call this.
- """
- cbook._warn_external(
- "'set_params()' not defined for locator of type " +
- str(type(self)))
- def __call__(self):
- """Return the locations of the ticks."""
- # note: some locators return data limits, other return view limits,
- # hence there is no *one* interface to call self.tick_values.
- raise NotImplementedError('Derived must override')
- def raise_if_exceeds(self, locs):
- """
- Log at WARNING level if *locs* is longer than `Locator.MAXTICKS`.
- This is intended to be called immediately before returning *locs* from
- ``__call__`` to inform users in case their Locator returns a huge
- number of ticks, causing Matplotlib to run out of memory.
- The "strange" name of this method dates back to when it would raise an
- exception instead of emitting a log.
- """
- if len(locs) >= self.MAXTICKS:
- _log.warning(
- "Locator attempting to generate %s ticks ([%s, ..., %s]), "
- "which exceeds Locator.MAXTICKS (%s).",
- len(locs), locs[0], locs[-1], self.MAXTICKS)
- return locs
- def nonsingular(self, v0, v1):
- """
- Adjust a range as needed to avoid singularities.
- This method gets called during autoscaling, with ``(v0, v1)`` set to
- the data limits on the axes if the axes contains any data, or
- ``(-inf, +inf)`` if not.
- - If ``v0 == v1`` (possibly up to some floating point slop), this
- method returns an expanded interval around this value.
- - If ``(v0, v1) == (-inf, +inf)``, this method returns appropriate
- default view limits.
- - Otherwise, ``(v0, v1)`` is returned without modification.
- """
- return mtransforms.nonsingular(v0, v1, expander=.05)
- def view_limits(self, vmin, vmax):
- """
- Select a scale for the range from vmin to vmax.
- Subclasses should override this method to change locator behaviour.
- """
- return mtransforms.nonsingular(vmin, vmax)
- @cbook.deprecated("3.2")
- def autoscale(self):
- """Autoscale the view limits."""
- return self.view_limits(*self.axis.get_view_interval())
- def pan(self, numsteps):
- """Pan numticks (can be positive or negative)"""
- ticks = self()
- numticks = len(ticks)
- vmin, vmax = self.axis.get_view_interval()
- vmin, vmax = mtransforms.nonsingular(vmin, vmax, expander=0.05)
- if numticks > 2:
- step = numsteps * abs(ticks[0] - ticks[1])
- else:
- d = abs(vmax - vmin)
- step = numsteps * d / 6.
- vmin += step
- vmax += step
- self.axis.set_view_interval(vmin, vmax, ignore=True)
- def zoom(self, direction):
- "Zoom in/out on axis; if direction is >0 zoom in, else zoom out"
- vmin, vmax = self.axis.get_view_interval()
- vmin, vmax = mtransforms.nonsingular(vmin, vmax, expander=0.05)
- interval = abs(vmax - vmin)
- step = 0.1 * interval * direction
- self.axis.set_view_interval(vmin + step, vmax - step, ignore=True)
- def refresh(self):
- """Refresh internal information based on current limits."""
- pass
- class IndexLocator(Locator):
- """
- Place a tick on every multiple of some base number of points
- plotted, e.g., on every 5th point. It is assumed that you are doing
- index plotting; i.e., the axis is 0, len(data). This is mainly
- useful for x ticks.
- """
- def __init__(self, base, offset):
- """Place ticks every *base* data point, starting at *offset*."""
- self._base = base
- self.offset = offset
- def set_params(self, base=None, offset=None):
- """Set parameters within this locator"""
- if base is not None:
- self._base = base
- if offset is not None:
- self.offset = offset
- def __call__(self):
- """Return the locations of the ticks"""
- dmin, dmax = self.axis.get_data_interval()
- return self.tick_values(dmin, dmax)
- def tick_values(self, vmin, vmax):
- return self.raise_if_exceeds(
- np.arange(vmin + self.offset, vmax + 1, self._base))
- class FixedLocator(Locator):
- """
- Tick locations are fixed. If nbins is not None,
- the array of possible positions will be subsampled to
- keep the number of ticks <= nbins +1.
- The subsampling will be done so as to include the smallest
- absolute value; for example, if zero is included in the
- array of possibilities, then it is guaranteed to be one of
- the chosen ticks.
- """
- def __init__(self, locs, nbins=None):
- self.locs = np.asarray(locs)
- self.nbins = max(nbins, 2) if nbins is not None else None
- def set_params(self, nbins=None):
- """Set parameters within this locator."""
- if nbins is not None:
- self.nbins = nbins
- def __call__(self):
- return self.tick_values(None, None)
- def tick_values(self, vmin, vmax):
- """"
- Return the locations of the ticks.
- .. note::
- Because the values are fixed, vmin and vmax are not used in this
- method.
- """
- if self.nbins is None:
- return self.locs
- step = max(int(np.ceil(len(self.locs) / self.nbins)), 1)
- ticks = self.locs[::step]
- for i in range(1, step):
- ticks1 = self.locs[i::step]
- if np.abs(ticks1).min() < np.abs(ticks).min():
- ticks = ticks1
- return self.raise_if_exceeds(ticks)
- class NullLocator(Locator):
- """
- No ticks
- """
- def __call__(self):
- return self.tick_values(None, None)
- def tick_values(self, vmin, vmax):
- """"
- Return the locations of the ticks.
- .. note::
- Because the values are Null, vmin and vmax are not used in this
- method.
- """
- return []
- class LinearLocator(Locator):
- """
- Determine the tick locations
- The first time this function is called it will try to set the
- number of ticks to make a nice tick partitioning. Thereafter the
- number of ticks will be fixed so that interactive navigation will
- be nice
- """
- def __init__(self, numticks=None, presets=None):
- """
- Use presets to set locs based on lom. A dict mapping vmin, vmax->locs
- """
- self.numticks = numticks
- if presets is None:
- self.presets = {}
- else:
- self.presets = presets
- @property
- def numticks(self):
- # Old hard-coded default.
- return self._numticks if self._numticks is not None else 11
- @numticks.setter
- def numticks(self, numticks):
- self._numticks = numticks
- def set_params(self, numticks=None, presets=None):
- """Set parameters within this locator."""
- if presets is not None:
- self.presets = presets
- if numticks is not None:
- self.numticks = numticks
- def __call__(self):
- 'Return the locations of the ticks'
- vmin, vmax = self.axis.get_view_interval()
- return self.tick_values(vmin, vmax)
- def tick_values(self, vmin, vmax):
- vmin, vmax = mtransforms.nonsingular(vmin, vmax, expander=0.05)
- if vmax < vmin:
- vmin, vmax = vmax, vmin
- if (vmin, vmax) in self.presets:
- return self.presets[(vmin, vmax)]
- if self.numticks == 0:
- return []
- ticklocs = np.linspace(vmin, vmax, self.numticks)
- return self.raise_if_exceeds(ticklocs)
- def view_limits(self, vmin, vmax):
- 'Try to choose the view limits intelligently'
- if vmax < vmin:
- vmin, vmax = vmax, vmin
- if vmin == vmax:
- vmin -= 1
- vmax += 1
- if rcParams['axes.autolimit_mode'] == 'round_numbers':
- exponent, remainder = divmod(
- math.log10(vmax - vmin), math.log10(max(self.numticks - 1, 1)))
- exponent -= (remainder < .5)
- scale = max(self.numticks - 1, 1) ** (-exponent)
- vmin = math.floor(scale * vmin) / scale
- vmax = math.ceil(scale * vmax) / scale
- return mtransforms.nonsingular(vmin, vmax)
- class MultipleLocator(Locator):
- """
- Set a tick on each integer multiple of a base within the view interval.
- """
- def __init__(self, base=1.0):
- self._edge = _Edge_integer(base, 0)
- def set_params(self, base):
- """Set parameters within this locator."""
- if base is not None:
- self._edge = _Edge_integer(base, 0)
- def __call__(self):
- 'Return the locations of the ticks'
- vmin, vmax = self.axis.get_view_interval()
- return self.tick_values(vmin, vmax)
- def tick_values(self, vmin, vmax):
- if vmax < vmin:
- vmin, vmax = vmax, vmin
- step = self._edge.step
- vmin = self._edge.ge(vmin) * step
- n = (vmax - vmin + 0.001 * step) // step
- locs = vmin - step + np.arange(n + 3) * step
- return self.raise_if_exceeds(locs)
- def view_limits(self, dmin, dmax):
- """
- Set the view limits to the nearest multiples of base that
- contain the data.
- """
- if rcParams['axes.autolimit_mode'] == 'round_numbers':
- vmin = self._edge.le(dmin) * self._edge.step
- vmax = self._edge.ge(dmax) * self._edge.step
- if vmin == vmax:
- vmin -= 1
- vmax += 1
- else:
- vmin = dmin
- vmax = dmax
- return mtransforms.nonsingular(vmin, vmax)
- def scale_range(vmin, vmax, n=1, threshold=100):
- dv = abs(vmax - vmin) # > 0 as nonsingular is called before.
- meanv = (vmax + vmin) / 2
- if abs(meanv) / dv < threshold:
- offset = 0
- else:
- offset = math.copysign(10 ** (math.log10(abs(meanv)) // 1), meanv)
- scale = 10 ** (math.log10(dv / n) // 1)
- return scale, offset
- class _Edge_integer:
- """
- Helper for MaxNLocator, MultipleLocator, etc.
- Take floating point precision limitations into account when calculating
- tick locations as integer multiples of a step.
- """
- def __init__(self, step, offset):
- """
- *step* is a positive floating-point interval between ticks.
- *offset* is the offset subtracted from the data limits
- prior to calculating tick locations.
- """
- if step <= 0:
- raise ValueError("'step' must be positive")
- self.step = step
- self._offset = abs(offset)
- def closeto(self, ms, edge):
- # Allow more slop when the offset is large compared to the step.
- if self._offset > 0:
- digits = np.log10(self._offset / self.step)
- tol = max(1e-10, 10 ** (digits - 12))
- tol = min(0.4999, tol)
- else:
- tol = 1e-10
- return abs(ms - edge) < tol
- def le(self, x):
- 'Return the largest n: n*step <= x.'
- d, m = divmod(x, self.step)
- if self.closeto(m / self.step, 1):
- return (d + 1)
- return d
- def ge(self, x):
- 'Return the smallest n: n*step >= x.'
- d, m = divmod(x, self.step)
- if self.closeto(m / self.step, 0):
- return d
- return (d + 1)
- class MaxNLocator(Locator):
- """
- Select no more than N intervals at nice locations.
- """
- default_params = dict(nbins=10,
- steps=None,
- integer=False,
- symmetric=False,
- prune=None,
- min_n_ticks=2)
- def __init__(self, *args, **kwargs):
- """
- Parameters
- ----------
- nbins : int or 'auto', optional, default: 10
- Maximum number of intervals; one less than max number of
- ticks. If the string `'auto'`, the number of bins will be
- automatically determined based on the length of the axis.
- steps : array-like, optional
- Sequence of nice numbers starting with 1 and ending with 10;
- e.g., [1, 2, 4, 5, 10], where the values are acceptable
- tick multiples. i.e. for the example, 20, 40, 60 would be
- an acceptable set of ticks, as would 0.4, 0.6, 0.8, because
- they are multiples of 2. However, 30, 60, 90 would not
- be allowed because 3 does not appear in the list of steps.
- integer : bool, optional, default: False
- If True, ticks will take only integer values, provided
- at least `min_n_ticks` integers are found within the
- view limits.
- symmetric : bool, optional, default: False
- If True, autoscaling will result in a range symmetric about zero.
- prune : {'lower', 'upper', 'both', None}, optional, default: None
- Remove edge ticks -- useful for stacked or ganged plots where
- the upper tick of one axes overlaps with the lower tick of the
- axes above it, primarily when :rc:`axes.autolimit_mode` is
- ``'round_numbers'``. If ``prune=='lower'``, the smallest tick will
- be removed. If ``prune == 'upper'``, the largest tick will be
- removed. If ``prune == 'both'``, the largest and smallest ticks
- will be removed. If ``prune == None``, no ticks will be removed.
- min_n_ticks : int, optional, default: 2
- Relax *nbins* and *integer* constraints if necessary to obtain
- this minimum number of ticks.
- """
- if args:
- if 'nbins' in kwargs:
- cbook.deprecated("3.1",
- message='Calling MaxNLocator with positional '
- 'and keyword parameter *nbins* is '
- 'considered an error and will fail '
- 'in future versions of matplotlib.')
- kwargs['nbins'] = args[0]
- if len(args) > 1:
- raise ValueError(
- "Keywords are required for all arguments except 'nbins'")
- self.set_params(**{**self.default_params, **kwargs})
- @staticmethod
- def _validate_steps(steps):
- if not np.iterable(steps):
- raise ValueError('steps argument must be an increasing sequence '
- 'of numbers between 1 and 10 inclusive')
- steps = np.asarray(steps)
- if np.any(np.diff(steps) <= 0) or steps[-1] > 10 or steps[0] < 1:
- raise ValueError('steps argument must be an increasing sequence '
- 'of numbers between 1 and 10 inclusive')
- if steps[0] != 1:
- steps = np.hstack((1, steps))
- if steps[-1] != 10:
- steps = np.hstack((steps, 10))
- return steps
- @staticmethod
- def _staircase(steps):
- # Make an extended staircase within which the needed
- # step will be found. This is probably much larger
- # than necessary.
- flights = (0.1 * steps[:-1], steps, 10 * steps[1])
- return np.hstack(flights)
- def set_params(self, **kwargs):
- """
- Set parameters for this locator.
- Parameters
- ----------
- nbins : int or 'auto', optional
- see `.MaxNLocator`
- steps : array-like, optional
- see `.MaxNLocator`
- integer : bool, optional
- see `.MaxNLocator`
- symmetric : bool, optional
- see `.MaxNLocator`
- prune : {'lower', 'upper', 'both', None}, optional
- see `.MaxNLocator`
- min_n_ticks : int, optional
- see `.MaxNLocator`
- """
- if 'nbins' in kwargs:
- self._nbins = kwargs.pop('nbins')
- if self._nbins != 'auto':
- self._nbins = int(self._nbins)
- if 'symmetric' in kwargs:
- self._symmetric = kwargs.pop('symmetric')
- if 'prune' in kwargs:
- prune = kwargs.pop('prune')
- cbook._check_in_list(['upper', 'lower', 'both', None], prune=prune)
- self._prune = prune
- if 'min_n_ticks' in kwargs:
- self._min_n_ticks = max(1, kwargs.pop('min_n_ticks'))
- if 'steps' in kwargs:
- steps = kwargs.pop('steps')
- if steps is None:
- self._steps = np.array([1, 1.5, 2, 2.5, 3, 4, 5, 6, 8, 10])
- else:
- self._steps = self._validate_steps(steps)
- self._extended_steps = self._staircase(self._steps)
- if 'integer' in kwargs:
- self._integer = kwargs.pop('integer')
- if kwargs:
- key, _ = kwargs.popitem()
- cbook.warn_deprecated("3.1",
- message="MaxNLocator.set_params got an "
- f"unexpected parameter: {key}")
- def _raw_ticks(self, vmin, vmax):
- """
- Generate a list of tick locations including the range *vmin* to
- *vmax*. In some applications, one or both of the end locations
- will not be needed, in which case they are trimmed off
- elsewhere.
- """
- if self._nbins == 'auto':
- if self.axis is not None:
- nbins = np.clip(self.axis.get_tick_space(),
- max(1, self._min_n_ticks - 1), 9)
- else:
- nbins = 9
- else:
- nbins = self._nbins
- scale, offset = scale_range(vmin, vmax, nbins)
- _vmin = vmin - offset
- _vmax = vmax - offset
- raw_step = (_vmax - _vmin) / nbins
- steps = self._extended_steps * scale
- if self._integer:
- # For steps > 1, keep only integer values.
- igood = (steps < 1) | (np.abs(steps - np.round(steps)) < 0.001)
- steps = steps[igood]
- istep = np.nonzero(steps >= raw_step)[0][0]
- # Classic round_numbers mode may require a larger step.
- if rcParams['axes.autolimit_mode'] == 'round_numbers':
- for istep in range(istep, len(steps)):
- step = steps[istep]
- best_vmin = (_vmin // step) * step
- best_vmax = best_vmin + step * nbins
- if best_vmax >= _vmax:
- break
- # This is an upper limit; move to smaller steps if necessary.
- for istep in reversed(range(istep + 1)):
- step = steps[istep]
- if (self._integer and
- np.floor(_vmax) - np.ceil(_vmin) >= self._min_n_ticks - 1):
- step = max(1, step)
- best_vmin = (_vmin // step) * step
- # Find tick locations spanning the vmin-vmax range, taking into
- # account degradation of precision when there is a large offset.
- # The edge ticks beyond vmin and/or vmax are needed for the
- # "round_numbers" autolimit mode.
- edge = _Edge_integer(step, offset)
- low = edge.le(_vmin - best_vmin)
- high = edge.ge(_vmax - best_vmin)
- ticks = np.arange(low, high + 1) * step + best_vmin
- # Count only the ticks that will be displayed.
- nticks = ((ticks <= _vmax) & (ticks >= _vmin)).sum()
- if nticks >= self._min_n_ticks:
- break
- return ticks + offset
- def __call__(self):
- vmin, vmax = self.axis.get_view_interval()
- return self.tick_values(vmin, vmax)
- def tick_values(self, vmin, vmax):
- if self._symmetric:
- vmax = max(abs(vmin), abs(vmax))
- vmin = -vmax
- vmin, vmax = mtransforms.nonsingular(
- vmin, vmax, expander=1e-13, tiny=1e-14)
- locs = self._raw_ticks(vmin, vmax)
- prune = self._prune
- if prune == 'lower':
- locs = locs[1:]
- elif prune == 'upper':
- locs = locs[:-1]
- elif prune == 'both':
- locs = locs[1:-1]
- return self.raise_if_exceeds(locs)
- def view_limits(self, dmin, dmax):
- if self._symmetric:
- dmax = max(abs(dmin), abs(dmax))
- dmin = -dmax
- dmin, dmax = mtransforms.nonsingular(
- dmin, dmax, expander=1e-12, tiny=1e-13)
- if rcParams['axes.autolimit_mode'] == 'round_numbers':
- return self._raw_ticks(dmin, dmax)[[0, -1]]
- else:
- return dmin, dmax
- @cbook.deprecated("3.1")
- def decade_down(x, base=10):
- """Floor x to the nearest lower decade."""
- if x == 0.0:
- return -base
- lx = np.floor(np.log(x) / np.log(base))
- return base ** lx
- @cbook.deprecated("3.1")
- def decade_up(x, base=10):
- """Ceil x to the nearest higher decade."""
- if x == 0.0:
- return base
- lx = np.ceil(np.log(x) / np.log(base))
- return base ** lx
- def is_decade(x, base=10, *, rtol=1e-10):
- if not np.isfinite(x):
- return False
- if x == 0.0:
- return True
- lx = np.log(np.abs(x)) / np.log(base)
- return is_close_to_int(lx, atol=rtol)
- def _decade_less_equal(x, base):
- """
- Return the largest integer power of *base* that's less or equal to *x*.
- If *x* is negative, the exponent will be *greater*.
- """
- return (x if x == 0 else
- -_decade_greater_equal(-x, base) if x < 0 else
- base ** np.floor(np.log(x) / np.log(base)))
- def _decade_greater_equal(x, base):
- """
- Return the smallest integer power of *base* that's greater or equal to *x*.
- If *x* is negative, the exponent will be *smaller*.
- """
- return (x if x == 0 else
- -_decade_less_equal(-x, base) if x < 0 else
- base ** np.ceil(np.log(x) / np.log(base)))
- def _decade_less(x, base):
- """
- Return the largest integer power of *base* that's less than *x*.
- If *x* is negative, the exponent will be *greater*.
- """
- if x < 0:
- return -_decade_greater(-x, base)
- less = _decade_less_equal(x, base)
- if less == x:
- less /= base
- return less
- def _decade_greater(x, base):
- """
- Return the smallest integer power of *base* that's greater than *x*.
- If *x* is negative, the exponent will be *smaller*.
- """
- if x < 0:
- return -_decade_less(-x, base)
- greater = _decade_greater_equal(x, base)
- if greater == x:
- greater *= base
- return greater
- def is_close_to_int(x, *, atol=1e-10):
- return abs(x - np.round(x)) < atol
- class LogLocator(Locator):
- """
- Determine the tick locations for log axes
- """
- def __init__(self, base=10.0, subs=(1.0,), numdecs=4, numticks=None):
- """
- Place ticks on the locations : subs[j] * base**i
- Parameters
- ----------
- subs : None, str, or sequence of float, optional, default (1.0,)
- Gives the multiples of integer powers of the base at which
- to place ticks. The default places ticks only at
- integer powers of the base.
- The permitted string values are ``'auto'`` and ``'all'``,
- both of which use an algorithm based on the axis view
- limits to determine whether and how to put ticks between
- integer powers of the base. With ``'auto'``, ticks are
- placed only between integer powers; with ``'all'``, the
- integer powers are included. A value of None is
- equivalent to ``'auto'``.
- """
- if numticks is None:
- if rcParams['_internal.classic_mode']:
- numticks = 15
- else:
- numticks = 'auto'
- self.base(base)
- self.subs(subs)
- self.numdecs = numdecs
- self.numticks = numticks
- def set_params(self, base=None, subs=None, numdecs=None, numticks=None):
- """Set parameters within this locator."""
- if base is not None:
- self.base(base)
- if subs is not None:
- self.subs(subs)
- if numdecs is not None:
- self.numdecs = numdecs
- if numticks is not None:
- self.numticks = numticks
- # FIXME: these base and subs functions are contrary to our
- # usual and desired API.
- def base(self, base):
- """Set the log base (major tick every ``base**i``, i integer)."""
- self._base = float(base)
- def subs(self, subs):
- """
- Set the minor ticks for the log scaling every ``base**i*subs[j]``.
- """
- if subs is None: # consistency with previous bad API
- self._subs = 'auto'
- elif isinstance(subs, str):
- cbook._check_in_list(('all', 'auto'), subs=subs)
- self._subs = subs
- else:
- try:
- self._subs = np.asarray(subs, dtype=float)
- except ValueError as e:
- raise ValueError("subs must be None, 'all', 'auto' or "
- "a sequence of floats, not "
- "{}.".format(subs)) from e
- if self._subs.ndim != 1:
- raise ValueError("A sequence passed to subs must be "
- "1-dimensional, not "
- "{}-dimensional.".format(self._subs.ndim))
- def __call__(self):
- 'Return the locations of the ticks'
- vmin, vmax = self.axis.get_view_interval()
- return self.tick_values(vmin, vmax)
- def tick_values(self, vmin, vmax):
- if self.numticks == 'auto':
- if self.axis is not None:
- numticks = np.clip(self.axis.get_tick_space(), 2, 9)
- else:
- numticks = 9
- else:
- numticks = self.numticks
- b = self._base
- # dummy axis has no axes attribute
- if hasattr(self.axis, 'axes') and self.axis.axes.name == 'polar':
- vmax = math.ceil(math.log(vmax) / math.log(b))
- decades = np.arange(vmax - self.numdecs, vmax)
- ticklocs = b ** decades
- return ticklocs
- if vmin <= 0.0:
- if self.axis is not None:
- vmin = self.axis.get_minpos()
- if vmin <= 0.0 or not np.isfinite(vmin):
- raise ValueError(
- "Data has no positive values, and therefore can not be "
- "log-scaled.")
- _log.debug('vmin %s vmax %s', vmin, vmax)
- if vmax < vmin:
- vmin, vmax = vmax, vmin
- log_vmin = math.log(vmin) / math.log(b)
- log_vmax = math.log(vmax) / math.log(b)
- numdec = math.floor(log_vmax) - math.ceil(log_vmin)
- if isinstance(self._subs, str):
- _first = 2.0 if self._subs == 'auto' else 1.0
- if numdec > 10 or b < 3:
- if self._subs == 'auto':
- return np.array([]) # no minor or major ticks
- else:
- subs = np.array([1.0]) # major ticks
- else:
- subs = np.arange(_first, b)
- else:
- subs = self._subs
- # Get decades between major ticks.
- stride = (max(math.ceil(numdec / (numticks - 1)), 1)
- if rcParams['_internal.classic_mode'] else
- (numdec + 1) // numticks + 1)
- # Does subs include anything other than 1? Essentially a hack to know
- # whether we're a major or a minor locator.
- have_subs = len(subs) > 1 or (len(subs) == 1 and subs[0] != 1.0)
- decades = np.arange(math.floor(log_vmin) - stride,
- math.ceil(log_vmax) + 2 * stride, stride)
- if hasattr(self, '_transform'):
- ticklocs = self._transform.inverted().transform(decades)
- if have_subs:
- if stride == 1:
- ticklocs = np.ravel(np.outer(subs, ticklocs))
- else:
- # No ticklocs if we have >1 decade between major ticks.
- ticklocs = np.array([])
- else:
- if have_subs:
- if stride == 1:
- ticklocs = np.concatenate(
- [subs * decade_start for decade_start in b ** decades])
- else:
- ticklocs = np.array([])
- else:
- ticklocs = b ** decades
- _log.debug('ticklocs %r', ticklocs)
- if (len(subs) > 1
- and stride == 1
- and ((vmin <= ticklocs) & (ticklocs <= vmax)).sum() <= 1):
- # If we're a minor locator *that expects at least two ticks per
- # decade* and the major locator stride is 1 and there's no more
- # than one minor tick, switch to AutoLocator.
- return AutoLocator().tick_values(vmin, vmax)
- else:
- return self.raise_if_exceeds(ticklocs)
- def view_limits(self, vmin, vmax):
- 'Try to choose the view limits intelligently'
- b = self._base
- vmin, vmax = self.nonsingular(vmin, vmax)
- if self.axis.axes.name == 'polar':
- vmax = math.ceil(math.log(vmax) / math.log(b))
- vmin = b ** (vmax - self.numdecs)
- if rcParams['axes.autolimit_mode'] == 'round_numbers':
- vmin = _decade_less_equal(vmin, self._base)
- vmax = _decade_greater_equal(vmax, self._base)
- return vmin, vmax
- def nonsingular(self, vmin, vmax):
- if vmin > vmax:
- vmin, vmax = vmax, vmin
- if not np.isfinite(vmin) or not np.isfinite(vmax):
- vmin, vmax = 1, 10 # Initial range, no data plotted yet.
- elif vmax <= 0:
- cbook._warn_external(
- "Data has no positive values, and therefore cannot be "
- "log-scaled.")
- vmin, vmax = 1, 10
- else:
- minpos = self.axis.get_minpos()
- if not np.isfinite(minpos):
- minpos = 1e-300 # This should never take effect.
- if vmin <= 0:
- vmin = minpos
- if vmin == vmax:
- vmin = _decade_less(vmin, self._base)
- vmax = _decade_greater(vmax, self._base)
- return vmin, vmax
- class SymmetricalLogLocator(Locator):
- """
- Determine the tick locations for symmetric log axes
- """
- def __init__(self, transform=None, subs=None, linthresh=None, base=None):
- """Place ticks on the locations ``base**i*subs[j]``."""
- if transform is not None:
- self._base = transform.base
- self._linthresh = transform.linthresh
- elif linthresh is not None and base is not None:
- self._base = base
- self._linthresh = linthresh
- else:
- raise ValueError("Either transform, or both linthresh "
- "and base, must be provided.")
- if subs is None:
- self._subs = [1.0]
- else:
- self._subs = subs
- self.numticks = 15
- def set_params(self, subs=None, numticks=None):
- """Set parameters within this locator."""
- if numticks is not None:
- self.numticks = numticks
- if subs is not None:
- self._subs = subs
- def __call__(self):
- """Return the locations of the ticks."""
- # Note, these are untransformed coordinates
- vmin, vmax = self.axis.get_view_interval()
- return self.tick_values(vmin, vmax)
- def tick_values(self, vmin, vmax):
- base = self._base
- linthresh = self._linthresh
- if vmax < vmin:
- vmin, vmax = vmax, vmin
- # The domain is divided into three sections, only some of
- # which may actually be present.
- #
- # <======== -t ==0== t ========>
- # aaaaaaaaa bbbbb ccccccccc
- #
- # a) and c) will have ticks at integral log positions. The
- # number of ticks needs to be reduced if there are more
- # than self.numticks of them.
- #
- # b) has a tick at 0 and only 0 (we assume t is a small
- # number, and the linear segment is just an implementation
- # detail and not interesting.)
- #
- # We could also add ticks at t, but that seems to usually be
- # uninteresting.
- #
- # "simple" mode is when the range falls entirely within (-t,
- # t) -- it should just display (vmin, 0, vmax)
- if -linthresh < vmin < vmax < linthresh:
- # only the linear range is present
- return [vmin, vmax]
- # Lower log range is present
- has_a = (vmin < -linthresh)
- # Upper log range is present
- has_c = (vmax > linthresh)
- # Check if linear range is present
- has_b = (has_a and vmax > -linthresh) or (has_c and vmin < linthresh)
- def get_log_range(lo, hi):
- lo = np.floor(np.log(lo) / np.log(base))
- hi = np.ceil(np.log(hi) / np.log(base))
- return lo, hi
- # Calculate all the ranges, so we can determine striding
- a_lo, a_hi = (0, 0)
- if has_a:
- a_upper_lim = min(-linthresh, vmax)
- a_lo, a_hi = get_log_range(np.abs(a_upper_lim), np.abs(vmin) + 1)
- c_lo, c_hi = (0, 0)
- if has_c:
- c_lower_lim = max(linthresh, vmin)
- c_lo, c_hi = get_log_range(c_lower_lim, vmax + 1)
- # Calculate the total number of integer exponents in a and c ranges
- total_ticks = (a_hi - a_lo) + (c_hi - c_lo)
- if has_b:
- total_ticks += 1
- stride = max(total_ticks // (self.numticks - 1), 1)
- decades = []
- if has_a:
- decades.extend(-1 * (base ** (np.arange(a_lo, a_hi,
- stride)[::-1])))
- if has_b:
- decades.append(0.0)
- if has_c:
- decades.extend(base ** (np.arange(c_lo, c_hi, stride)))
- # Add the subticks if requested
- if self._subs is None:
- subs = np.arange(2.0, base)
- else:
- subs = np.asarray(self._subs)
- if len(subs) > 1 or subs[0] != 1.0:
- ticklocs = []
- for decade in decades:
- if decade == 0:
- ticklocs.append(decade)
- else:
- ticklocs.extend(subs * decade)
- else:
- ticklocs = decades
- return self.raise_if_exceeds(np.array(ticklocs))
- def view_limits(self, vmin, vmax):
- 'Try to choose the view limits intelligently'
- b = self._base
- if vmax < vmin:
- vmin, vmax = vmax, vmin
- if rcParams['axes.autolimit_mode'] == 'round_numbers':
- vmin = _decade_less_equal(vmin, b)
- vmax = _decade_greater_equal(vmax, b)
- if vmin == vmax:
- vmin = _decade_less(vmin, b)
- vmax = _decade_greater(vmax, b)
- result = mtransforms.nonsingular(vmin, vmax)
- return result
- class LogitLocator(MaxNLocator):
- """
- Determine the tick locations for logit axes
- """
- def __init__(self, minor=False, *, nbins="auto"):
- """
- Place ticks on the logit locations
- Parameters
- ----------
- nbins : int or 'auto', optional
- Number of ticks. Only used if minor is False.
- minor : bool, default: False
- Indicate if this locator is for minor ticks or not.
- """
- self._minor = minor
- MaxNLocator.__init__(self, nbins=nbins, steps=[1, 2, 5, 10])
- def set_params(self, minor=None, **kwargs):
- """Set parameters within this locator."""
- if minor is not None:
- self._minor = minor
- MaxNLocator.set_params(self, **kwargs)
- @property
- def minor(self):
- return self._minor
- @minor.setter
- def minor(self, value):
- self.set_params(minor=value)
- def tick_values(self, vmin, vmax):
- # dummy axis has no axes attribute
- if hasattr(self.axis, "axes") and self.axis.axes.name == "polar":
- raise NotImplementedError("Polar axis cannot be logit scaled yet")
- if self._nbins == "auto":
- if self.axis is not None:
- nbins = self.axis.get_tick_space()
- if nbins < 2:
- nbins = 2
- else:
- nbins = 9
- else:
- nbins = self._nbins
- # We define ideal ticks with their index:
- # linscale: ... 1e-3 1e-2 1e-1 1/2 1-1e-1 1-1e-2 1-1e-3 ...
- # b-scale : ... -3 -2 -1 0 1 2 3 ...
- def ideal_ticks(x):
- return 10 ** x if x < 0 else 1 - (10 ** (-x)) if x > 0 else 1 / 2
- vmin, vmax = self.nonsingular(vmin, vmax)
- binf = int(
- np.floor(np.log10(vmin))
- if vmin < 0.5
- else 0
- if vmin < 0.9
- else -np.ceil(np.log10(1 - vmin))
- )
- bsup = int(
- np.ceil(np.log10(vmax))
- if vmax <= 0.5
- else 1
- if vmax <= 0.9
- else -np.floor(np.log10(1 - vmax))
- )
- numideal = bsup - binf - 1
- if numideal >= 2:
- # have 2 or more wanted ideal ticks, so use them as major ticks
- if numideal > nbins:
- # to many ideal ticks, subsampling ideals for major ticks, and
- # take others for minor ticks
- subsampling_factor = math.ceil(numideal / nbins)
- if self._minor:
- ticklocs = [
- ideal_ticks(b)
- for b in range(binf, bsup + 1)
- if (b % subsampling_factor) != 0
- ]
- else:
- ticklocs = [
- ideal_ticks(b)
- for b in range(binf, bsup + 1)
- if (b % subsampling_factor) == 0
- ]
- return self.raise_if_exceeds(np.array(ticklocs))
- if self._minor:
- ticklocs = []
- for b in range(binf, bsup):
- if b < -1:
- ticklocs.extend(np.arange(2, 10) * 10 ** b)
- elif b == -1:
- ticklocs.extend(np.arange(2, 5) / 10)
- elif b == 0:
- ticklocs.extend(np.arange(6, 9) / 10)
- else:
- ticklocs.extend(
- 1 - np.arange(2, 10)[::-1] * 10 ** (-b - 1)
- )
- return self.raise_if_exceeds(np.array(ticklocs))
- ticklocs = [ideal_ticks(b) for b in range(binf, bsup + 1)]
- return self.raise_if_exceeds(np.array(ticklocs))
- # the scale is zoomed so same ticks as linear scale can be used
- if self._minor:
- return []
- return MaxNLocator.tick_values(self, vmin, vmax)
- def nonsingular(self, vmin, vmax):
- standard_minpos = 1e-7
- initial_range = (standard_minpos, 1 - standard_minpos)
- if vmin > vmax:
- vmin, vmax = vmax, vmin
- if not np.isfinite(vmin) or not np.isfinite(vmax):
- vmin, vmax = initial_range # Initial range, no data plotted yet.
- elif vmax <= 0 or vmin >= 1:
- # vmax <= 0 occurs when all values are negative
- # vmin >= 1 occurs when all values are greater than one
- cbook._warn_external(
- "Data has no values between 0 and 1, and therefore cannot be "
- "logit-scaled."
- )
- vmin, vmax = initial_range
- else:
- minpos = (
- self.axis.get_minpos()
- if self.axis is not None
- else standard_minpos
- )
- if not np.isfinite(minpos):
- minpos = standard_minpos # This should never take effect.
- if vmin <= 0:
- vmin = minpos
- # NOTE: for vmax, we should query a property similar to get_minpos,
- # but related to the maximal, less-than-one data point.
- # Unfortunately, Bbox._minpos is defined very deep in the BBox and
- # updated with data, so for now we use 1 - minpos as a substitute.
- if vmax >= 1:
- vmax = 1 - minpos
- if vmin == vmax:
- vmin, vmax = 0.1 * vmin, 1 - 0.1 * vmin
- return vmin, vmax
- class AutoLocator(MaxNLocator):
- """
- Dynamically find major tick positions. This is actually a subclass
- of `~matplotlib.ticker.MaxNLocator`, with parameters *nbins = 'auto'*
- and *steps = [1, 2, 2.5, 5, 10]*.
- """
- def __init__(self):
- """
- To know the values of the non-public parameters, please have a
- look to the defaults of `~matplotlib.ticker.MaxNLocator`.
- """
- if rcParams['_internal.classic_mode']:
- nbins = 9
- steps = [1, 2, 5, 10]
- else:
- nbins = 'auto'
- steps = [1, 2, 2.5, 5, 10]
- MaxNLocator.__init__(self, nbins=nbins, steps=steps)
- class AutoMinorLocator(Locator):
- """
- Dynamically find minor tick positions based on the positions of
- major ticks. The scale must be linear with major ticks evenly spaced.
- """
- def __init__(self, n=None):
- """
- *n* is the number of subdivisions of the interval between
- major ticks; e.g., n=2 will place a single minor tick midway
- between major ticks.
- If *n* is omitted or None, it will be set to 5 or 4.
- """
- self.ndivs = n
- def __call__(self):
- 'Return the locations of the ticks'
- if self.axis.get_scale() == 'log':
- cbook._warn_external('AutoMinorLocator does not work with '
- 'logarithmic scale')
- return []
- majorlocs = self.axis.get_majorticklocs()
- try:
- majorstep = majorlocs[1] - majorlocs[0]
- except IndexError:
- # Need at least two major ticks to find minor tick locations
- # TODO: Figure out a way to still be able to display minor
- # ticks without two major ticks visible. For now, just display
- # no ticks at all.
- return []
- if self.ndivs is None:
- majorstep_no_exponent = 10 ** (np.log10(majorstep) % 1)
- if np.isclose(majorstep_no_exponent, [1.0, 2.5, 5.0, 10.0]).any():
- ndivs = 5
- else:
- ndivs = 4
- else:
- ndivs = self.ndivs
- minorstep = majorstep / ndivs
- vmin, vmax = self.axis.get_view_interval()
- if vmin > vmax:
- vmin, vmax = vmax, vmin
- t0 = majorlocs[0]
- tmin = ((vmin - t0) // minorstep + 1) * minorstep
- tmax = ((vmax - t0) // minorstep + 1) * minorstep
- locs = np.arange(tmin, tmax, minorstep) + t0
- return self.raise_if_exceeds(locs)
- def tick_values(self, vmin, vmax):
- raise NotImplementedError('Cannot get tick locations for a '
- '%s type.' % type(self))
- class OldAutoLocator(Locator):
- """
- On autoscale this class picks the best MultipleLocator to set the
- view limits and the tick locs.
- """
- def __init__(self):
- self._locator = LinearLocator()
- def __call__(self):
- 'Return the locations of the ticks'
- self.refresh()
- return self.raise_if_exceeds(self._locator())
- def tick_values(self, vmin, vmax):
- raise NotImplementedError('Cannot get tick locations for a '
- '%s type.' % type(self))
- def refresh(self):
- # docstring inherited
- vmin, vmax = self.axis.get_view_interval()
- vmin, vmax = mtransforms.nonsingular(vmin, vmax, expander=0.05)
- d = abs(vmax - vmin)
- self._locator = self.get_locator(d)
- def view_limits(self, vmin, vmax):
- 'Try to choose the view limits intelligently'
- d = abs(vmax - vmin)
- self._locator = self.get_locator(d)
- return self._locator.view_limits(vmin, vmax)
- def get_locator(self, d):
- """Pick the best locator based on a distance *d*."""
- d = abs(d)
- if d <= 0:
- locator = MultipleLocator(0.2)
- else:
- try:
- ld = math.log10(d)
- except OverflowError:
- raise RuntimeError('AutoLocator illegal data interval range')
- fld = math.floor(ld)
- base = 10 ** fld
- #if ld==fld: base = 10**(fld-1)
- #else: base = 10**fld
- if d >= 5 * base:
- ticksize = base
- elif d >= 2 * base:
- ticksize = base / 2.0
- else:
- ticksize = base / 5.0
- locator = MultipleLocator(ticksize)
- return locator
|