123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317431843194320432143224323432443254326432743284329433043314332433343344335433643374338433943404341434243434344434543464347434843494350435143524353435443554356435743584359436043614362436343644365436643674368436943704371437243734374437543764377437843794380438143824383438443854386438743884389439043914392439343944395439643974398439944004401440244034404440544064407440844094410441144124413441444154416441744184419442044214422442344244425442644274428442944304431443244334434443544364437443844394440444144424443444444454446444744484449445044514452445344544455445644574458445944604461446244634464446544664467446844694470447144724473447444754476447744784479448044814482448344844485448644874488448944904491449244934494449544964497449844994500450145024503450445054506450745084509451045114512451345144515451645174518451945204521452245234524452545264527452845294530453145324533453445354536453745384539454045414542454345444545454645474548454945504551455245534554455545564557455845594560456145624563456445654566456745684569457045714572457345744575457645774578457945804581458245834584458545864587458845894590459145924593459445954596459745984599460046014602460346044605460646074608460946104611461246134614461546164617461846194620462146224623462446254626462746284629463046314632463346344635463646374638463946404641464246434644 |
- from collections.abc import Iterable, Sequence
- from contextlib import ExitStack
- import functools
- import inspect
- import logging
- from numbers import Real
- from operator import attrgetter
- import types
- import numpy as np
- import matplotlib as mpl
- from matplotlib import _api, cbook, _docstring, offsetbox
- import matplotlib.artist as martist
- import matplotlib.axis as maxis
- from matplotlib.cbook import _OrderedSet, _check_1d, index_of
- import matplotlib.collections as mcoll
- import matplotlib.colors as mcolors
- import matplotlib.font_manager as font_manager
- from matplotlib.gridspec import SubplotSpec
- import matplotlib.image as mimage
- import matplotlib.lines as mlines
- import matplotlib.patches as mpatches
- from matplotlib.rcsetup import cycler, validate_axisbelow
- import matplotlib.spines as mspines
- import matplotlib.table as mtable
- import matplotlib.text as mtext
- import matplotlib.ticker as mticker
- import matplotlib.transforms as mtransforms
- _log = logging.getLogger(__name__)
- class _axis_method_wrapper:
- """
- Helper to generate Axes methods wrapping Axis methods.
- After ::
- get_foo = _axis_method_wrapper("xaxis", "get_bar")
- (in the body of a class) ``get_foo`` is a method that forwards it arguments
- to the ``get_bar`` method of the ``xaxis`` attribute, and gets its
- signature and docstring from ``Axis.get_bar``.
- The docstring of ``get_foo`` is built by replacing "this Axis" by "the
- {attr_name}" (i.e., "the xaxis", "the yaxis") in the wrapped method's
- dedented docstring; additional replacements can be given in *doc_sub*.
- """
- def __init__(self, attr_name, method_name, *, doc_sub=None):
- self.attr_name = attr_name
- self.method_name = method_name
- # Immediately put the docstring in ``self.__doc__`` so that docstring
- # manipulations within the class body work as expected.
- doc = inspect.getdoc(getattr(maxis.Axis, method_name))
- self._missing_subs = []
- if doc:
- doc_sub = {"this Axis": f"the {self.attr_name}", **(doc_sub or {})}
- for k, v in doc_sub.items():
- if k not in doc: # Delay raising error until we know qualname.
- self._missing_subs.append(k)
- doc = doc.replace(k, v)
- self.__doc__ = doc
- def __set_name__(self, owner, name):
- # This is called at the end of the class body as
- # ``self.__set_name__(cls, name_under_which_self_is_assigned)``; we
- # rely on that to give the wrapper the correct __name__/__qualname__.
- get_method = attrgetter(f"{self.attr_name}.{self.method_name}")
- def wrapper(self, *args, **kwargs):
- return get_method(self)(*args, **kwargs)
- wrapper.__module__ = owner.__module__
- wrapper.__name__ = name
- wrapper.__qualname__ = f"{owner.__qualname__}.{name}"
- wrapper.__doc__ = self.__doc__
- # Manually copy the signature instead of using functools.wraps because
- # displaying the Axis method source when asking for the Axes method
- # source would be confusing.
- wrapper.__signature__ = inspect.signature(
- getattr(maxis.Axis, self.method_name))
- if self._missing_subs:
- raise ValueError(
- "The definition of {} expected that the docstring of Axis.{} "
- "contains {!r} as substrings".format(
- wrapper.__qualname__, self.method_name,
- ", ".join(map(repr, self._missing_subs))))
- setattr(owner, name, wrapper)
- class _TransformedBoundsLocator:
- """
- Axes locator for `.Axes.inset_axes` and similarly positioned Axes.
- The locator is a callable object used in `.Axes.set_aspect` to compute the
- Axes location depending on the renderer.
- """
- def __init__(self, bounds, transform):
- """
- *bounds* (a ``[l, b, w, h]`` rectangle) and *transform* together
- specify the position of the inset Axes.
- """
- self._bounds = bounds
- self._transform = transform
- def __call__(self, ax, renderer):
- # Subtracting transSubfigure will typically rely on inverted(),
- # freezing the transform; thus, this needs to be delayed until draw
- # time as transSubfigure may otherwise change after this is evaluated.
- return mtransforms.TransformedBbox(
- mtransforms.Bbox.from_bounds(*self._bounds),
- self._transform - ax.figure.transSubfigure)
- def _process_plot_format(fmt, *, ambiguous_fmt_datakey=False):
- """
- Convert a MATLAB style color/line style format string to a (*linestyle*,
- *marker*, *color*) tuple.
- Example format strings include:
- * 'ko': black circles
- * '.b': blue dots
- * 'r--': red dashed lines
- * 'C2--': the third color in the color cycle, dashed lines
- The format is absolute in the sense that if a linestyle or marker is not
- defined in *fmt*, there is no line or marker. This is expressed by
- returning 'None' for the respective quantity.
- See Also
- --------
- matplotlib.Line2D.lineStyles, matplotlib.colors.cnames
- All possible styles and color format strings.
- """
- linestyle = None
- marker = None
- color = None
- # Is fmt just a colorspec?
- try:
- color = mcolors.to_rgba(fmt)
- # We need to differentiate grayscale '1.0' from tri_down marker '1'
- try:
- fmtint = str(int(fmt))
- except ValueError:
- return linestyle, marker, color # Yes
- else:
- if fmt != fmtint:
- # user definitely doesn't want tri_down marker
- return linestyle, marker, color # Yes
- else:
- # ignore converted color
- color = None
- except ValueError:
- pass # No, not just a color.
- errfmt = ("{!r} is neither a data key nor a valid format string ({})"
- if ambiguous_fmt_datakey else
- "{!r} is not a valid format string ({})")
- i = 0
- while i < len(fmt):
- c = fmt[i]
- if fmt[i:i+2] in mlines.lineStyles: # First, the two-char styles.
- if linestyle is not None:
- raise ValueError(errfmt.format(fmt, "two linestyle symbols"))
- linestyle = fmt[i:i+2]
- i += 2
- elif c in mlines.lineStyles:
- if linestyle is not None:
- raise ValueError(errfmt.format(fmt, "two linestyle symbols"))
- linestyle = c
- i += 1
- elif c in mlines.lineMarkers:
- if marker is not None:
- raise ValueError(errfmt.format(fmt, "two marker symbols"))
- marker = c
- i += 1
- elif c in mcolors.get_named_colors_mapping():
- if color is not None:
- raise ValueError(errfmt.format(fmt, "two color symbols"))
- color = c
- i += 1
- elif c == 'C' and i < len(fmt) - 1:
- color_cycle_number = int(fmt[i + 1])
- color = mcolors.to_rgba(f"C{color_cycle_number}")
- i += 2
- else:
- raise ValueError(
- errfmt.format(fmt, f"unrecognized character {c!r}"))
- if linestyle is None and marker is None:
- linestyle = mpl.rcParams['lines.linestyle']
- if linestyle is None:
- linestyle = 'None'
- if marker is None:
- marker = 'None'
- return linestyle, marker, color
- class _process_plot_var_args:
- """
- Process variable length arguments to `~.Axes.plot`, to support ::
- plot(t, s)
- plot(t1, s1, t2, s2)
- plot(t1, s1, 'ko', t2, s2)
- plot(t1, s1, 'ko', t2, s2, 'r--', t3, e3)
- an arbitrary number of *x*, *y*, *fmt* are allowed
- """
- def __init__(self, command='plot'):
- self.command = command
- self.set_prop_cycle(None)
- def set_prop_cycle(self, cycler):
- if cycler is None:
- cycler = mpl.rcParams['axes.prop_cycle']
- self._idx = 0
- self._cycler_items = [*cycler]
- self._prop_keys = cycler.keys # This should make a copy
- def __call__(self, axes, *args, data=None, **kwargs):
- axes._process_unit_info(kwargs=kwargs)
- for pos_only in "xy":
- if pos_only in kwargs:
- raise _api.kwarg_error(self.command, pos_only)
- if not args:
- return
- if data is None: # Process dict views
- args = [cbook.sanitize_sequence(a) for a in args]
- else: # Process the 'data' kwarg.
- replaced = [mpl._replacer(data, arg) for arg in args]
- if len(args) == 1:
- label_namer_idx = 0
- elif len(args) == 2: # Can be x, y or y, c.
- # Figure out what the second argument is.
- # 1) If the second argument cannot be a format shorthand, the
- # second argument is the label_namer.
- # 2) Otherwise (it could have been a format shorthand),
- # a) if we did perform a substitution, emit a warning, and
- # use it as label_namer.
- # b) otherwise, it is indeed a format shorthand; use the
- # first argument as label_namer.
- try:
- _process_plot_format(args[1])
- except ValueError: # case 1)
- label_namer_idx = 1
- else:
- if replaced[1] is not args[1]: # case 2a)
- _api.warn_external(
- f"Second argument {args[1]!r} is ambiguous: could "
- f"be a format string but is in 'data'; using as "
- f"data. If it was intended as data, set the "
- f"format string to an empty string to suppress "
- f"this warning. If it was intended as a format "
- f"string, explicitly pass the x-values as well. "
- f"Alternatively, rename the entry in 'data'.",
- RuntimeWarning)
- label_namer_idx = 1
- else: # case 2b)
- label_namer_idx = 0
- elif len(args) == 3:
- label_namer_idx = 1
- else:
- raise ValueError(
- "Using arbitrary long args with data is not supported due "
- "to ambiguity of arguments; use multiple plotting calls "
- "instead")
- if kwargs.get("label") is None:
- kwargs["label"] = mpl._label_from_arg(
- replaced[label_namer_idx], args[label_namer_idx])
- args = replaced
- ambiguous_fmt_datakey = data is not None and len(args) == 2
- if len(args) >= 4 and not cbook.is_scalar_or_string(
- kwargs.get("label")):
- raise ValueError("plot() with multiple groups of data (i.e., "
- "pairs of x and y) does not support multiple "
- "labels")
- # Repeatedly grab (x, y) or (x, y, format) from the front of args and
- # massage them into arguments to plot() or fill().
- while args:
- this, args = args[:2], args[2:]
- if args and isinstance(args[0], str):
- this += args[0],
- args = args[1:]
- yield from self._plot_args(
- axes, this, kwargs, ambiguous_fmt_datakey=ambiguous_fmt_datakey)
- def get_next_color(self):
- """Return the next color in the cycle."""
- if 'color' not in self._prop_keys:
- return 'k'
- c = self._cycler_items[self._idx]['color']
- self._idx = (self._idx + 1) % len(self._cycler_items)
- return c
- def _getdefaults(self, ignore, kw):
- """
- If some keys in the property cycle (excluding those in the set
- *ignore*) are absent or set to None in the dict *kw*, return a copy
- of the next entry in the property cycle, excluding keys in *ignore*.
- Otherwise, don't advance the property cycle, and return an empty dict.
- """
- prop_keys = self._prop_keys - ignore
- if any(kw.get(k, None) is None for k in prop_keys):
- # Need to copy this dictionary or else the next time around
- # in the cycle, the dictionary could be missing entries.
- default_dict = self._cycler_items[self._idx].copy()
- self._idx = (self._idx + 1) % len(self._cycler_items)
- for p in ignore:
- default_dict.pop(p, None)
- else:
- default_dict = {}
- return default_dict
- def _setdefaults(self, defaults, kw):
- """
- Add to the dict *kw* the entries in the dict *default* that are absent
- or set to None in *kw*.
- """
- for k in defaults:
- if kw.get(k, None) is None:
- kw[k] = defaults[k]
- def _makeline(self, axes, x, y, kw, kwargs):
- kw = {**kw, **kwargs} # Don't modify the original kw.
- default_dict = self._getdefaults(set(), kw)
- self._setdefaults(default_dict, kw)
- seg = mlines.Line2D(x, y, **kw)
- return seg, kw
- def _makefill(self, axes, x, y, kw, kwargs):
- # Polygon doesn't directly support unitized inputs.
- x = axes.convert_xunits(x)
- y = axes.convert_yunits(y)
- kw = kw.copy() # Don't modify the original kw.
- kwargs = kwargs.copy()
- # Ignore 'marker'-related properties as they aren't Polygon
- # properties, but they are Line2D properties, and so they are
- # likely to appear in the default cycler construction.
- # This is done here to the defaults dictionary as opposed to the
- # other two dictionaries because we do want to capture when a
- # *user* explicitly specifies a marker which should be an error.
- # We also want to prevent advancing the cycler if there are no
- # defaults needed after ignoring the given properties.
- ignores = {'marker', 'markersize', 'markeredgecolor',
- 'markerfacecolor', 'markeredgewidth'}
- # Also ignore anything provided by *kwargs*.
- for k, v in kwargs.items():
- if v is not None:
- ignores.add(k)
- # Only using the first dictionary to use as basis
- # for getting defaults for back-compat reasons.
- # Doing it with both seems to mess things up in
- # various places (probably due to logic bugs elsewhere).
- default_dict = self._getdefaults(ignores, kw)
- self._setdefaults(default_dict, kw)
- # Looks like we don't want "color" to be interpreted to
- # mean both facecolor and edgecolor for some reason.
- # So the "kw" dictionary is thrown out, and only its
- # 'color' value is kept and translated as a 'facecolor'.
- # This design should probably be revisited as it increases
- # complexity.
- facecolor = kw.get('color', None)
- # Throw out 'color' as it is now handled as a facecolor
- default_dict.pop('color', None)
- # To get other properties set from the cycler
- # modify the kwargs dictionary.
- self._setdefaults(default_dict, kwargs)
- seg = mpatches.Polygon(np.column_stack((x, y)),
- facecolor=facecolor,
- fill=kwargs.get('fill', True),
- closed=kw['closed'])
- seg.set(**kwargs)
- return seg, kwargs
- def _plot_args(self, axes, tup, kwargs, *,
- return_kwargs=False, ambiguous_fmt_datakey=False):
- """
- Process the arguments of ``plot([x], y, [fmt], **kwargs)`` calls.
- This processes a single set of ([x], y, [fmt]) parameters; i.e. for
- ``plot(x, y, x2, y2)`` it will be called twice. Once for (x, y) and
- once for (x2, y2).
- x and y may be 2D and thus can still represent multiple datasets.
- For multiple datasets, if the keyword argument *label* is a list, this
- will unpack the list and assign the individual labels to the datasets.
- Parameters
- ----------
- tup : tuple
- A tuple of the positional parameters. This can be one of
- - (y,)
- - (x, y)
- - (y, fmt)
- - (x, y, fmt)
- kwargs : dict
- The keyword arguments passed to ``plot()``.
- return_kwargs : bool
- Whether to also return the effective keyword arguments after label
- unpacking as well.
- ambiguous_fmt_datakey : bool
- Whether the format string in *tup* could also have been a
- misspelled data key.
- Returns
- -------
- result
- If *return_kwargs* is false, a list of Artists representing the
- dataset(s).
- If *return_kwargs* is true, a list of (Artist, effective_kwargs)
- representing the dataset(s). See *return_kwargs*.
- The Artist is either `.Line2D` (if called from ``plot()``) or
- `.Polygon` otherwise.
- """
- if len(tup) > 1 and isinstance(tup[-1], str):
- # xy is tup with fmt stripped (could still be (y,) only)
- *xy, fmt = tup
- linestyle, marker, color = _process_plot_format(
- fmt, ambiguous_fmt_datakey=ambiguous_fmt_datakey)
- elif len(tup) == 3:
- raise ValueError('third arg must be a format string')
- else:
- xy = tup
- linestyle, marker, color = None, None, None
- # Don't allow any None value; these would be up-converted to one
- # element array of None which causes problems downstream.
- if any(v is None for v in tup):
- raise ValueError("x, y, and format string must not be None")
- kw = {}
- for prop_name, val in zip(('linestyle', 'marker', 'color'),
- (linestyle, marker, color)):
- if val is not None:
- # check for conflicts between fmt and kwargs
- if (fmt.lower() != 'none'
- and prop_name in kwargs
- and val != 'None'):
- # Technically ``plot(x, y, 'o', ls='--')`` is a conflict
- # because 'o' implicitly unsets the linestyle
- # (linestyle='None').
- # We'll gracefully not warn in this case because an
- # explicit set via kwargs can be seen as intention to
- # override an implicit unset.
- # Note: We don't val.lower() != 'none' because val is not
- # necessarily a string (can be a tuple for colors). This
- # is safe, because *val* comes from _process_plot_format()
- # which only returns 'None'.
- _api.warn_external(
- f"{prop_name} is redundantly defined by the "
- f"'{prop_name}' keyword argument and the fmt string "
- f'"{fmt}" (-> {prop_name}={val!r}). The keyword '
- f"argument will take precedence.")
- kw[prop_name] = val
- if len(xy) == 2:
- x = _check_1d(xy[0])
- y = _check_1d(xy[1])
- else:
- x, y = index_of(xy[-1])
- if axes.xaxis is not None:
- axes.xaxis.update_units(x)
- if axes.yaxis is not None:
- axes.yaxis.update_units(y)
- if x.shape[0] != y.shape[0]:
- raise ValueError(f"x and y must have same first dimension, but "
- f"have shapes {x.shape} and {y.shape}")
- if x.ndim > 2 or y.ndim > 2:
- raise ValueError(f"x and y can be no greater than 2D, but have "
- f"shapes {x.shape} and {y.shape}")
- if x.ndim == 1:
- x = x[:, np.newaxis]
- if y.ndim == 1:
- y = y[:, np.newaxis]
- if self.command == 'plot':
- make_artist = self._makeline
- else:
- kw['closed'] = kwargs.get('closed', True)
- make_artist = self._makefill
- ncx, ncy = x.shape[1], y.shape[1]
- if ncx > 1 and ncy > 1 and ncx != ncy:
- raise ValueError(f"x has {ncx} columns but y has {ncy} columns")
- if ncx == 0 or ncy == 0:
- return []
- label = kwargs.get('label')
- n_datasets = max(ncx, ncy)
- if n_datasets > 1 and not cbook.is_scalar_or_string(label):
- if len(label) != n_datasets:
- raise ValueError(f"label must be scalar or have the same "
- f"length as the input data, but found "
- f"{len(label)} for {n_datasets} datasets.")
- labels = label
- else:
- labels = [label] * n_datasets
- result = (make_artist(axes, x[:, j % ncx], y[:, j % ncy], kw,
- {**kwargs, 'label': label})
- for j, label in enumerate(labels))
- if return_kwargs:
- return list(result)
- else:
- return [l[0] for l in result]
- @_api.define_aliases({"facecolor": ["fc"]})
- class _AxesBase(martist.Artist):
- name = "rectilinear"
- # axis names are the prefixes for the attributes that contain the
- # respective axis; e.g. 'x' <-> self.xaxis, containing an XAxis.
- # Note that PolarAxes uses these attributes as well, so that we have
- # 'x' <-> self.xaxis, containing a ThetaAxis. In particular we do not
- # have 'theta' in _axis_names.
- # In practice, this is ('x', 'y') for all 2D Axes and ('x', 'y', 'z')
- # for Axes3D.
- _axis_names = ("x", "y")
- _shared_axes = {name: cbook.Grouper() for name in _axis_names}
- _twinned_axes = cbook.Grouper()
- _subclass_uses_cla = False
- @property
- def _axis_map(self):
- """A mapping of axis names, e.g. 'x', to `Axis` instances."""
- return {name: getattr(self, f"{name}axis")
- for name in self._axis_names}
- def __str__(self):
- return "{0}({1[0]:g},{1[1]:g};{1[2]:g}x{1[3]:g})".format(
- type(self).__name__, self._position.bounds)
- def __init__(self, fig,
- *args,
- facecolor=None, # defaults to rc axes.facecolor
- frameon=True,
- sharex=None, # use Axes instance's xaxis info
- sharey=None, # use Axes instance's yaxis info
- label='',
- xscale=None,
- yscale=None,
- box_aspect=None,
- **kwargs
- ):
- """
- Build an Axes in a figure.
- Parameters
- ----------
- fig : `~matplotlib.figure.Figure`
- The Axes is built in the `.Figure` *fig*.
- *args
- ``*args`` can be a single ``(left, bottom, width, height)``
- rectangle or a single `.Bbox`. This specifies the rectangle (in
- figure coordinates) where the Axes is positioned.
- ``*args`` can also consist of three numbers or a single three-digit
- number; in the latter case, the digits are considered as
- independent numbers. The numbers are interpreted as ``(nrows,
- ncols, index)``: ``(nrows, ncols)`` specifies the size of an array
- of subplots, and ``index`` is the 1-based index of the subplot
- being created. Finally, ``*args`` can also directly be a
- `.SubplotSpec` instance.
- sharex, sharey : `~matplotlib.axes.Axes`, optional
- The x- or y-`~.matplotlib.axis` is shared with the x- or y-axis in
- the input `~.axes.Axes`.
- frameon : bool, default: True
- Whether the Axes frame is visible.
- box_aspect : float, optional
- Set a fixed aspect for the Axes box, i.e. the ratio of height to
- width. See `~.axes.Axes.set_box_aspect` for details.
- **kwargs
- Other optional keyword arguments:
- %(Axes:kwdoc)s
- Returns
- -------
- `~.axes.Axes`
- The new `~.axes.Axes` object.
- """
- super().__init__()
- if "rect" in kwargs:
- if args:
- raise TypeError(
- "'rect' cannot be used together with positional arguments")
- rect = kwargs.pop("rect")
- _api.check_isinstance((mtransforms.Bbox, Iterable), rect=rect)
- args = (rect,)
- subplotspec = None
- if len(args) == 1 and isinstance(args[0], mtransforms.Bbox):
- self._position = args[0]
- elif len(args) == 1 and np.iterable(args[0]):
- self._position = mtransforms.Bbox.from_bounds(*args[0])
- else:
- self._position = self._originalPosition = mtransforms.Bbox.unit()
- subplotspec = SubplotSpec._from_subplot_args(fig, args)
- if self._position.width < 0 or self._position.height < 0:
- raise ValueError('Width and height specified must be non-negative')
- self._originalPosition = self._position.frozen()
- self.axes = self
- self._aspect = 'auto'
- self._adjustable = 'box'
- self._anchor = 'C'
- self._stale_viewlims = {name: False for name in self._axis_names}
- self._sharex = sharex
- self._sharey = sharey
- self.set_label(label)
- self.set_figure(fig)
- # The subplotspec needs to be set after the figure (so that
- # figure-level subplotpars are taken into account), but the figure
- # needs to be set after self._position is initialized.
- if subplotspec:
- self.set_subplotspec(subplotspec)
- else:
- self._subplotspec = None
- self.set_box_aspect(box_aspect)
- self._axes_locator = None # Optionally set via update(kwargs).
- self._children = []
- # placeholder for any colorbars added that use this Axes.
- # (see colorbar.py):
- self._colorbars = []
- self.spines = mspines.Spines.from_dict(self._gen_axes_spines())
- # this call may differ for non-sep axes, e.g., polar
- self._init_axis()
- if facecolor is None:
- facecolor = mpl.rcParams['axes.facecolor']
- self._facecolor = facecolor
- self._frameon = frameon
- self.set_axisbelow(mpl.rcParams['axes.axisbelow'])
- self._rasterization_zorder = None
- self.clear()
- # funcs used to format x and y - fall back on major formatters
- self.fmt_xdata = None
- self.fmt_ydata = None
- self.set_navigate(True)
- self.set_navigate_mode(None)
- if xscale:
- self.set_xscale(xscale)
- if yscale:
- self.set_yscale(yscale)
- self._internal_update(kwargs)
- for name, axis in self._axis_map.items():
- axis.callbacks._connect_picklable(
- 'units', self._unit_change_handler(name))
- rcParams = mpl.rcParams
- self.tick_params(
- top=rcParams['xtick.top'] and rcParams['xtick.minor.top'],
- bottom=rcParams['xtick.bottom'] and rcParams['xtick.minor.bottom'],
- labeltop=(rcParams['xtick.labeltop'] and
- rcParams['xtick.minor.top']),
- labelbottom=(rcParams['xtick.labelbottom'] and
- rcParams['xtick.minor.bottom']),
- left=rcParams['ytick.left'] and rcParams['ytick.minor.left'],
- right=rcParams['ytick.right'] and rcParams['ytick.minor.right'],
- labelleft=(rcParams['ytick.labelleft'] and
- rcParams['ytick.minor.left']),
- labelright=(rcParams['ytick.labelright'] and
- rcParams['ytick.minor.right']),
- which='minor')
- self.tick_params(
- top=rcParams['xtick.top'] and rcParams['xtick.major.top'],
- bottom=rcParams['xtick.bottom'] and rcParams['xtick.major.bottom'],
- labeltop=(rcParams['xtick.labeltop'] and
- rcParams['xtick.major.top']),
- labelbottom=(rcParams['xtick.labelbottom'] and
- rcParams['xtick.major.bottom']),
- left=rcParams['ytick.left'] and rcParams['ytick.major.left'],
- right=rcParams['ytick.right'] and rcParams['ytick.major.right'],
- labelleft=(rcParams['ytick.labelleft'] and
- rcParams['ytick.major.left']),
- labelright=(rcParams['ytick.labelright'] and
- rcParams['ytick.major.right']),
- which='major')
- def __init_subclass__(cls, **kwargs):
- parent_uses_cla = super(cls, cls)._subclass_uses_cla
- if 'cla' in cls.__dict__:
- _api.warn_deprecated(
- '3.6',
- pending=True,
- message=f'Overriding `Axes.cla` in {cls.__qualname__} is '
- 'pending deprecation in %(since)s and will be fully '
- 'deprecated in favor of `Axes.clear` in the future. '
- 'Please report '
- f'this to the {cls.__module__!r} author.')
- cls._subclass_uses_cla = 'cla' in cls.__dict__ or parent_uses_cla
- super().__init_subclass__(**kwargs)
- def __getstate__(self):
- state = super().__getstate__()
- # Prune the sharing & twinning info to only contain the current group.
- state["_shared_axes"] = {
- name: self._shared_axes[name].get_siblings(self)
- for name in self._axis_names if self in self._shared_axes[name]}
- state["_twinned_axes"] = (self._twinned_axes.get_siblings(self)
- if self in self._twinned_axes else None)
- return state
- def __setstate__(self, state):
- # Merge the grouping info back into the global groupers.
- shared_axes = state.pop("_shared_axes")
- for name, shared_siblings in shared_axes.items():
- self._shared_axes[name].join(*shared_siblings)
- twinned_siblings = state.pop("_twinned_axes")
- if twinned_siblings:
- self._twinned_axes.join(*twinned_siblings)
- self.__dict__ = state
- self._stale = True
- def __repr__(self):
- fields = []
- if self.get_label():
- fields += [f"label={self.get_label()!r}"]
- if hasattr(self, "get_title"):
- titles = {}
- for k in ["left", "center", "right"]:
- title = self.get_title(loc=k)
- if title:
- titles[k] = title
- if titles:
- fields += [f"title={titles}"]
- for name, axis in self._axis_map.items():
- if axis.get_label() and axis.get_label().get_text():
- fields += [f"{name}label={axis.get_label().get_text()!r}"]
- return f"<{self.__class__.__name__}: " + ", ".join(fields) + ">"
- def get_subplotspec(self):
- """Return the `.SubplotSpec` associated with the subplot, or None."""
- return self._subplotspec
- def set_subplotspec(self, subplotspec):
- """Set the `.SubplotSpec`. associated with the subplot."""
- self._subplotspec = subplotspec
- self._set_position(subplotspec.get_position(self.figure))
- def get_gridspec(self):
- """Return the `.GridSpec` associated with the subplot, or None."""
- return self._subplotspec.get_gridspec() if self._subplotspec else None
- def get_window_extent(self, renderer=None):
- """
- Return the Axes bounding box in display space.
- This bounding box does not include the spines, ticks, ticklabels,
- or other labels. For a bounding box including these elements use
- `~matplotlib.axes.Axes.get_tightbbox`.
- See Also
- --------
- matplotlib.axes.Axes.get_tightbbox
- matplotlib.axis.Axis.get_tightbbox
- matplotlib.spines.Spine.get_window_extent
- """
- return self.bbox
- def _init_axis(self):
- # This is moved out of __init__ because non-separable axes don't use it
- self.xaxis = maxis.XAxis(self, clear=False)
- self.spines.bottom.register_axis(self.xaxis)
- self.spines.top.register_axis(self.xaxis)
- self.yaxis = maxis.YAxis(self, clear=False)
- self.spines.left.register_axis(self.yaxis)
- self.spines.right.register_axis(self.yaxis)
- def set_figure(self, fig):
- # docstring inherited
- super().set_figure(fig)
- self.bbox = mtransforms.TransformedBbox(self._position,
- fig.transSubfigure)
- # these will be updated later as data is added
- self.dataLim = mtransforms.Bbox.null()
- self._viewLim = mtransforms.Bbox.unit()
- self.transScale = mtransforms.TransformWrapper(
- mtransforms.IdentityTransform())
- self._set_lim_and_transforms()
- def _unstale_viewLim(self):
- # We should arrange to store this information once per share-group
- # instead of on every axis.
- need_scale = {
- name: any(ax._stale_viewlims[name]
- for ax in self._shared_axes[name].get_siblings(self))
- for name in self._axis_names}
- if any(need_scale.values()):
- for name in need_scale:
- for ax in self._shared_axes[name].get_siblings(self):
- ax._stale_viewlims[name] = False
- self.autoscale_view(**{f"scale{name}": scale
- for name, scale in need_scale.items()})
- @property
- def viewLim(self):
- self._unstale_viewLim()
- return self._viewLim
- def _request_autoscale_view(self, axis="all", tight=None):
- """
- Mark a single axis, or all of them, as stale wrt. autoscaling.
- No computation is performed until the next autoscaling; thus, separate
- calls to control individual axises incur negligible performance cost.
- Parameters
- ----------
- axis : str, default: "all"
- Either an element of ``self._axis_names``, or "all".
- tight : bool or None, default: None
- """
- axis_names = _api.check_getitem(
- {**{k: [k] for k in self._axis_names}, "all": self._axis_names},
- axis=axis)
- for name in axis_names:
- self._stale_viewlims[name] = True
- if tight is not None:
- self._tight = tight
- def _set_lim_and_transforms(self):
- """
- Set the *_xaxis_transform*, *_yaxis_transform*, *transScale*,
- *transData*, *transLimits* and *transAxes* transformations.
- .. note::
- This method is primarily used by rectilinear projections of the
- `~matplotlib.axes.Axes` class, and is meant to be overridden by
- new kinds of projection Axes that need different transformations
- and limits. (See `~matplotlib.projections.polar.PolarAxes` for an
- example.)
- """
- self.transAxes = mtransforms.BboxTransformTo(self.bbox)
- # Transforms the x and y axis separately by a scale factor.
- # It is assumed that this part will have non-linear components
- # (e.g., for a log scale).
- self.transScale = mtransforms.TransformWrapper(
- mtransforms.IdentityTransform())
- # An affine transformation on the data, generally to limit the
- # range of the axes
- self.transLimits = mtransforms.BboxTransformFrom(
- mtransforms.TransformedBbox(self._viewLim, self.transScale))
- # The parentheses are important for efficiency here -- they
- # group the last two (which are usually affines) separately
- # from the first (which, with log-scaling can be non-affine).
- self.transData = self.transScale + (self.transLimits + self.transAxes)
- self._xaxis_transform = mtransforms.blended_transform_factory(
- self.transData, self.transAxes)
- self._yaxis_transform = mtransforms.blended_transform_factory(
- self.transAxes, self.transData)
- def get_xaxis_transform(self, which='grid'):
- """
- Get the transformation used for drawing x-axis labels, ticks
- and gridlines. The x-direction is in data coordinates and the
- y-direction is in axis coordinates.
- .. note::
- This transformation is primarily used by the
- `~matplotlib.axis.Axis` class, and is meant to be
- overridden by new kinds of projections that may need to
- place axis elements in different locations.
- Parameters
- ----------
- which : {'grid', 'tick1', 'tick2'}
- """
- if which == 'grid':
- return self._xaxis_transform
- elif which == 'tick1':
- # for cartesian projection, this is bottom spine
- return self.spines.bottom.get_spine_transform()
- elif which == 'tick2':
- # for cartesian projection, this is top spine
- return self.spines.top.get_spine_transform()
- else:
- raise ValueError(f'unknown value for which: {which!r}')
- def get_xaxis_text1_transform(self, pad_points):
- """
- Returns
- -------
- transform : Transform
- The transform used for drawing x-axis labels, which will add
- *pad_points* of padding (in points) between the axis and the label.
- The x-direction is in data coordinates and the y-direction is in
- axis coordinates
- valign : {'center', 'top', 'bottom', 'baseline', 'center_baseline'}
- The text vertical alignment.
- halign : {'center', 'left', 'right'}
- The text horizontal alignment.
- Notes
- -----
- This transformation is primarily used by the `~matplotlib.axis.Axis`
- class, and is meant to be overridden by new kinds of projections that
- may need to place axis elements in different locations.
- """
- labels_align = mpl.rcParams["xtick.alignment"]
- return (self.get_xaxis_transform(which='tick1') +
- mtransforms.ScaledTranslation(0, -1 * pad_points / 72,
- self.figure.dpi_scale_trans),
- "top", labels_align)
- def get_xaxis_text2_transform(self, pad_points):
- """
- Returns
- -------
- transform : Transform
- The transform used for drawing secondary x-axis labels, which will
- add *pad_points* of padding (in points) between the axis and the
- label. The x-direction is in data coordinates and the y-direction
- is in axis coordinates
- valign : {'center', 'top', 'bottom', 'baseline', 'center_baseline'}
- The text vertical alignment.
- halign : {'center', 'left', 'right'}
- The text horizontal alignment.
- Notes
- -----
- This transformation is primarily used by the `~matplotlib.axis.Axis`
- class, and is meant to be overridden by new kinds of projections that
- may need to place axis elements in different locations.
- """
- labels_align = mpl.rcParams["xtick.alignment"]
- return (self.get_xaxis_transform(which='tick2') +
- mtransforms.ScaledTranslation(0, pad_points / 72,
- self.figure.dpi_scale_trans),
- "bottom", labels_align)
- def get_yaxis_transform(self, which='grid'):
- """
- Get the transformation used for drawing y-axis labels, ticks
- and gridlines. The x-direction is in axis coordinates and the
- y-direction is in data coordinates.
- .. note::
- This transformation is primarily used by the
- `~matplotlib.axis.Axis` class, and is meant to be
- overridden by new kinds of projections that may need to
- place axis elements in different locations.
- Parameters
- ----------
- which : {'grid', 'tick1', 'tick2'}
- """
- if which == 'grid':
- return self._yaxis_transform
- elif which == 'tick1':
- # for cartesian projection, this is bottom spine
- return self.spines.left.get_spine_transform()
- elif which == 'tick2':
- # for cartesian projection, this is top spine
- return self.spines.right.get_spine_transform()
- else:
- raise ValueError(f'unknown value for which: {which!r}')
- def get_yaxis_text1_transform(self, pad_points):
- """
- Returns
- -------
- transform : Transform
- The transform used for drawing y-axis labels, which will add
- *pad_points* of padding (in points) between the axis and the label.
- The x-direction is in axis coordinates and the y-direction is in
- data coordinates
- valign : {'center', 'top', 'bottom', 'baseline', 'center_baseline'}
- The text vertical alignment.
- halign : {'center', 'left', 'right'}
- The text horizontal alignment.
- Notes
- -----
- This transformation is primarily used by the `~matplotlib.axis.Axis`
- class, and is meant to be overridden by new kinds of projections that
- may need to place axis elements in different locations.
- """
- labels_align = mpl.rcParams["ytick.alignment"]
- return (self.get_yaxis_transform(which='tick1') +
- mtransforms.ScaledTranslation(-1 * pad_points / 72, 0,
- self.figure.dpi_scale_trans),
- labels_align, "right")
- def get_yaxis_text2_transform(self, pad_points):
- """
- Returns
- -------
- transform : Transform
- The transform used for drawing secondart y-axis labels, which will
- add *pad_points* of padding (in points) between the axis and the
- label. The x-direction is in axis coordinates and the y-direction
- is in data coordinates
- valign : {'center', 'top', 'bottom', 'baseline', 'center_baseline'}
- The text vertical alignment.
- halign : {'center', 'left', 'right'}
- The text horizontal alignment.
- Notes
- -----
- This transformation is primarily used by the `~matplotlib.axis.Axis`
- class, and is meant to be overridden by new kinds of projections that
- may need to place axis elements in different locations.
- """
- labels_align = mpl.rcParams["ytick.alignment"]
- return (self.get_yaxis_transform(which='tick2') +
- mtransforms.ScaledTranslation(pad_points / 72, 0,
- self.figure.dpi_scale_trans),
- labels_align, "left")
- def _update_transScale(self):
- self.transScale.set(
- mtransforms.blended_transform_factory(
- self.xaxis.get_transform(), self.yaxis.get_transform()))
- def get_position(self, original=False):
- """
- Return the position of the Axes within the figure as a `.Bbox`.
- Parameters
- ----------
- original : bool
- If ``True``, return the original position. Otherwise, return the
- active position. For an explanation of the positions see
- `.set_position`.
- Returns
- -------
- `.Bbox`
- """
- if original:
- return self._originalPosition.frozen()
- else:
- locator = self.get_axes_locator()
- if not locator:
- self.apply_aspect()
- return self._position.frozen()
- def set_position(self, pos, which='both'):
- """
- Set the Axes position.
- Axes have two position attributes. The 'original' position is the
- position allocated for the Axes. The 'active' position is the
- position the Axes is actually drawn at. These positions are usually
- the same unless a fixed aspect is set to the Axes. See
- `.Axes.set_aspect` for details.
- Parameters
- ----------
- pos : [left, bottom, width, height] or `~matplotlib.transforms.Bbox`
- The new position of the Axes in `.Figure` coordinates.
- which : {'both', 'active', 'original'}, default: 'both'
- Determines which position variables to change.
- See Also
- --------
- matplotlib.transforms.Bbox.from_bounds
- matplotlib.transforms.Bbox.from_extents
- """
- self._set_position(pos, which=which)
- # because this is being called externally to the library we
- # don't let it be in the layout.
- self.set_in_layout(False)
- def _set_position(self, pos, which='both'):
- """
- Private version of set_position.
- Call this internally to get the same functionality of `set_position`,
- but not to take the axis out of the constrained_layout hierarchy.
- """
- if not isinstance(pos, mtransforms.BboxBase):
- pos = mtransforms.Bbox.from_bounds(*pos)
- for ax in self._twinned_axes.get_siblings(self):
- if which in ('both', 'active'):
- ax._position.set(pos)
- if which in ('both', 'original'):
- ax._originalPosition.set(pos)
- self.stale = True
- def reset_position(self):
- """
- Reset the active position to the original position.
- This undoes changes to the active position (as defined in
- `.set_position`) which may have been performed to satisfy fixed-aspect
- constraints.
- """
- for ax in self._twinned_axes.get_siblings(self):
- pos = ax.get_position(original=True)
- ax.set_position(pos, which='active')
- def set_axes_locator(self, locator):
- """
- Set the Axes locator.
- Parameters
- ----------
- locator : Callable[[Axes, Renderer], Bbox]
- """
- self._axes_locator = locator
- self.stale = True
- def get_axes_locator(self):
- """
- Return the axes_locator.
- """
- return self._axes_locator
- def _set_artist_props(self, a):
- """Set the boilerplate props for artists added to Axes."""
- a.set_figure(self.figure)
- if not a.is_transform_set():
- a.set_transform(self.transData)
- a.axes = self
- if a.get_mouseover():
- self._mouseover_set.add(a)
- def _gen_axes_patch(self):
- """
- Returns
- -------
- Patch
- The patch used to draw the background of the Axes. It is also used
- as the clipping path for any data elements on the Axes.
- In the standard Axes, this is a rectangle, but in other projections
- it may not be.
- Notes
- -----
- Intended to be overridden by new projection types.
- """
- return mpatches.Rectangle((0.0, 0.0), 1.0, 1.0)
- def _gen_axes_spines(self, locations=None, offset=0.0, units='inches'):
- """
- Returns
- -------
- dict
- Mapping of spine names to `.Line2D` or `.Patch` instances that are
- used to draw Axes spines.
- In the standard Axes, spines are single line segments, but in other
- projections they may not be.
- Notes
- -----
- Intended to be overridden by new projection types.
- """
- return {side: mspines.Spine.linear_spine(self, side)
- for side in ['left', 'right', 'bottom', 'top']}
- def sharex(self, other):
- """
- Share the x-axis with *other*.
- This is equivalent to passing ``sharex=other`` when constructing the
- Axes, and cannot be used if the x-axis is already being shared with
- another Axes.
- """
- _api.check_isinstance(_AxesBase, other=other)
- if self._sharex is not None and other is not self._sharex:
- raise ValueError("x-axis is already shared")
- self._shared_axes["x"].join(self, other)
- self._sharex = other
- self.xaxis.major = other.xaxis.major # Ticker instances holding
- self.xaxis.minor = other.xaxis.minor # locator and formatter.
- x0, x1 = other.get_xlim()
- self.set_xlim(x0, x1, emit=False, auto=other.get_autoscalex_on())
- self.xaxis._scale = other.xaxis._scale
- def sharey(self, other):
- """
- Share the y-axis with *other*.
- This is equivalent to passing ``sharey=other`` when constructing the
- Axes, and cannot be used if the y-axis is already being shared with
- another Axes.
- """
- _api.check_isinstance(_AxesBase, other=other)
- if self._sharey is not None and other is not self._sharey:
- raise ValueError("y-axis is already shared")
- self._shared_axes["y"].join(self, other)
- self._sharey = other
- self.yaxis.major = other.yaxis.major # Ticker instances holding
- self.yaxis.minor = other.yaxis.minor # locator and formatter.
- y0, y1 = other.get_ylim()
- self.set_ylim(y0, y1, emit=False, auto=other.get_autoscaley_on())
- self.yaxis._scale = other.yaxis._scale
- def __clear(self):
- """Clear the Axes."""
- # The actual implementation of clear() as long as clear() has to be
- # an adapter delegating to the correct implementation.
- # The implementation can move back into clear() when the
- # deprecation on cla() subclassing expires.
- # stash the current visibility state
- if hasattr(self, 'patch'):
- patch_visible = self.patch.get_visible()
- else:
- patch_visible = True
- xaxis_visible = self.xaxis.get_visible()
- yaxis_visible = self.yaxis.get_visible()
- for axis in self._axis_map.values():
- axis.clear() # Also resets the scale to linear.
- for spine in self.spines.values():
- spine._clear() # Use _clear to not clear Axis again
- self.ignore_existing_data_limits = True
- self.callbacks = cbook.CallbackRegistry(
- signals=["xlim_changed", "ylim_changed", "zlim_changed"])
- # update the minor locator for x and y axis based on rcParams
- if mpl.rcParams['xtick.minor.visible']:
- self.xaxis.set_minor_locator(mticker.AutoMinorLocator())
- if mpl.rcParams['ytick.minor.visible']:
- self.yaxis.set_minor_locator(mticker.AutoMinorLocator())
- self._xmargin = mpl.rcParams['axes.xmargin']
- self._ymargin = mpl.rcParams['axes.ymargin']
- self._tight = None
- self._use_sticky_edges = True
- self._get_lines = _process_plot_var_args()
- self._get_patches_for_fill = _process_plot_var_args('fill')
- self._gridOn = mpl.rcParams['axes.grid']
- old_children, self._children = self._children, []
- for chld in old_children:
- chld.axes = chld.figure = None
- self._mouseover_set = _OrderedSet()
- self.child_axes = []
- self._current_image = None # strictly for pyplot via _sci, _gci
- self._projection_init = None # strictly for pyplot.subplot
- self.legend_ = None
- self.containers = []
- self.grid(False) # Disable grid on init to use rcParameter
- self.grid(self._gridOn, which=mpl.rcParams['axes.grid.which'],
- axis=mpl.rcParams['axes.grid.axis'])
- props = font_manager.FontProperties(
- size=mpl.rcParams['axes.titlesize'],
- weight=mpl.rcParams['axes.titleweight'])
- y = mpl.rcParams['axes.titley']
- if y is None:
- y = 1.0
- self._autotitlepos = True
- else:
- self._autotitlepos = False
- self.title = mtext.Text(
- x=0.5, y=y, text='',
- fontproperties=props,
- verticalalignment='baseline',
- horizontalalignment='center',
- )
- self._left_title = mtext.Text(
- x=0.0, y=y, text='',
- fontproperties=props.copy(),
- verticalalignment='baseline',
- horizontalalignment='left', )
- self._right_title = mtext.Text(
- x=1.0, y=y, text='',
- fontproperties=props.copy(),
- verticalalignment='baseline',
- horizontalalignment='right',
- )
- title_offset_points = mpl.rcParams['axes.titlepad']
- # refactor this out so it can be called in ax.set_title if
- # pad argument used...
- self._set_title_offset_trans(title_offset_points)
- for _title in (self.title, self._left_title, self._right_title):
- self._set_artist_props(_title)
- # The patch draws the background of the Axes. We want this to be below
- # the other artists. We use the frame to draw the edges so we are
- # setting the edgecolor to None.
- self.patch = self._gen_axes_patch()
- self.patch.set_figure(self.figure)
- self.patch.set_facecolor(self._facecolor)
- self.patch.set_edgecolor('none')
- self.patch.set_linewidth(0)
- self.patch.set_transform(self.transAxes)
- self.set_axis_on()
- self.xaxis.set_clip_path(self.patch)
- self.yaxis.set_clip_path(self.patch)
- if self._sharex is not None:
- self.xaxis.set_visible(xaxis_visible)
- self.patch.set_visible(patch_visible)
- if self._sharey is not None:
- self.yaxis.set_visible(yaxis_visible)
- self.patch.set_visible(patch_visible)
- # This comes last, as the call to _set_lim may trigger an autoscale (in
- # case of shared axes), requiring children to be already set up.
- for name, axis in self._axis_map.items():
- share = getattr(self, f"_share{name}")
- if share is not None:
- getattr(self, f"share{name}")(share)
- else:
- # Although the scale was set to linear as part of clear,
- # polar requires that _set_scale is called again
- if self.name == "polar":
- axis._set_scale("linear")
- axis._set_lim(0, 1, auto=True)
- self._update_transScale()
- self.stale = True
- def clear(self):
- """Clear the Axes."""
- # Act as an alias, or as the superclass implementation depending on the
- # subclass implementation.
- if self._subclass_uses_cla:
- self.cla()
- else:
- self.__clear()
- def cla(self):
- """Clear the Axes."""
- # Act as an alias, or as the superclass implementation depending on the
- # subclass implementation.
- if self._subclass_uses_cla:
- self.__clear()
- else:
- self.clear()
- class ArtistList(Sequence):
- """
- A sublist of Axes children based on their type.
- The type-specific children sublists were made immutable in Matplotlib
- 3.7. In the future these artist lists may be replaced by tuples. Use
- as if this is a tuple already.
- """
- def __init__(self, axes, prop_name,
- valid_types=None, invalid_types=None):
- """
- Parameters
- ----------
- axes : `~matplotlib.axes.Axes`
- The Axes from which this sublist will pull the children
- Artists.
- prop_name : str
- The property name used to access this sublist from the Axes;
- used to generate deprecation warnings.
- valid_types : list of type, optional
- A list of types that determine which children will be returned
- by this sublist. If specified, then the Artists in the sublist
- must be instances of any of these types. If unspecified, then
- any type of Artist is valid (unless limited by
- *invalid_types*.)
- invalid_types : tuple, optional
- A list of types that determine which children will *not* be
- returned by this sublist. If specified, then Artists in the
- sublist will never be an instance of these types. Otherwise, no
- types will be excluded.
- """
- self._axes = axes
- self._prop_name = prop_name
- self._type_check = lambda artist: (
- (not valid_types or isinstance(artist, valid_types)) and
- (not invalid_types or not isinstance(artist, invalid_types))
- )
- def __repr__(self):
- return f'<Axes.ArtistList of {len(self)} {self._prop_name}>'
- def __len__(self):
- return sum(self._type_check(artist)
- for artist in self._axes._children)
- def __iter__(self):
- for artist in list(self._axes._children):
- if self._type_check(artist):
- yield artist
- def __getitem__(self, key):
- return [artist
- for artist in self._axes._children
- if self._type_check(artist)][key]
- def __add__(self, other):
- if isinstance(other, (list, _AxesBase.ArtistList)):
- return [*self, *other]
- if isinstance(other, (tuple, _AxesBase.ArtistList)):
- return (*self, *other)
- return NotImplemented
- def __radd__(self, other):
- if isinstance(other, list):
- return other + list(self)
- if isinstance(other, tuple):
- return other + tuple(self)
- return NotImplemented
- @property
- def artists(self):
- return self.ArtistList(self, 'artists', invalid_types=(
- mcoll.Collection, mimage.AxesImage, mlines.Line2D, mpatches.Patch,
- mtable.Table, mtext.Text))
- @property
- def collections(self):
- return self.ArtistList(self, 'collections',
- valid_types=mcoll.Collection)
- @property
- def images(self):
- return self.ArtistList(self, 'images', valid_types=mimage.AxesImage)
- @property
- def lines(self):
- return self.ArtistList(self, 'lines', valid_types=mlines.Line2D)
- @property
- def patches(self):
- return self.ArtistList(self, 'patches', valid_types=mpatches.Patch)
- @property
- def tables(self):
- return self.ArtistList(self, 'tables', valid_types=mtable.Table)
- @property
- def texts(self):
- return self.ArtistList(self, 'texts', valid_types=mtext.Text)
- def get_facecolor(self):
- """Get the facecolor of the Axes."""
- return self.patch.get_facecolor()
- def set_facecolor(self, color):
- """
- Set the facecolor of the Axes.
- Parameters
- ----------
- color : color
- """
- self._facecolor = color
- self.stale = True
- return self.patch.set_facecolor(color)
- def _set_title_offset_trans(self, title_offset_points):
- """
- Set the offset for the title either from :rc:`axes.titlepad`
- or from set_title kwarg ``pad``.
- """
- self.titleOffsetTrans = mtransforms.ScaledTranslation(
- 0.0, title_offset_points / 72,
- self.figure.dpi_scale_trans)
- for _title in (self.title, self._left_title, self._right_title):
- _title.set_transform(self.transAxes + self.titleOffsetTrans)
- _title.set_clip_box(None)
- def set_prop_cycle(self, *args, **kwargs):
- """
- Set the property cycle of the Axes.
- The property cycle controls the style properties such as color,
- marker and linestyle of future plot commands. The style properties
- of data already added to the Axes are not modified.
- Call signatures::
- set_prop_cycle(cycler)
- set_prop_cycle(label=values[, label2=values2[, ...]])
- set_prop_cycle(label, values)
- Form 1 sets given `~cycler.Cycler` object.
- Form 2 creates a `~cycler.Cycler` which cycles over one or more
- properties simultaneously and set it as the property cycle of the
- Axes. If multiple properties are given, their value lists must have
- the same length. This is just a shortcut for explicitly creating a
- cycler and passing it to the function, i.e. it's short for
- ``set_prop_cycle(cycler(label=values label2=values2, ...))``.
- Form 3 creates a `~cycler.Cycler` for a single property and set it
- as the property cycle of the Axes. This form exists for compatibility
- with the original `cycler.cycler` interface. Its use is discouraged
- in favor of the kwarg form, i.e. ``set_prop_cycle(label=values)``.
- Parameters
- ----------
- cycler : `~cycler.Cycler`
- Set the given Cycler. *None* resets to the cycle defined by the
- current style.
- .. ACCEPTS: `~cycler.Cycler`
- label : str
- The property key. Must be a valid `.Artist` property.
- For example, 'color' or 'linestyle'. Aliases are allowed,
- such as 'c' for 'color' and 'lw' for 'linewidth'.
- values : iterable
- Finite-length iterable of the property values. These values
- are validated and will raise a ValueError if invalid.
- See Also
- --------
- matplotlib.rcsetup.cycler
- Convenience function for creating validated cyclers for properties.
- cycler.cycler
- The original function for creating unvalidated cyclers.
- Examples
- --------
- Setting the property cycle for a single property:
- >>> ax.set_prop_cycle(color=['red', 'green', 'blue'])
- Setting the property cycle for simultaneously cycling over multiple
- properties (e.g. red circle, green plus, blue cross):
- >>> ax.set_prop_cycle(color=['red', 'green', 'blue'],
- ... marker=['o', '+', 'x'])
- """
- if args and kwargs:
- raise TypeError("Cannot supply both positional and keyword "
- "arguments to this method.")
- # Can't do `args == (None,)` as that crashes cycler.
- if len(args) == 1 and args[0] is None:
- prop_cycle = None
- else:
- prop_cycle = cycler(*args, **kwargs)
- self._get_lines.set_prop_cycle(prop_cycle)
- self._get_patches_for_fill.set_prop_cycle(prop_cycle)
- def get_aspect(self):
- """
- Return the aspect ratio of the axes scaling.
- This is either "auto" or a float giving the ratio of y/x-scale.
- """
- return self._aspect
- def set_aspect(self, aspect, adjustable=None, anchor=None, share=False):
- """
- Set the aspect ratio of the axes scaling, i.e. y/x-scale.
- Parameters
- ----------
- aspect : {'auto', 'equal'} or float
- Possible values:
- - 'auto': fill the position rectangle with data.
- - 'equal': same as ``aspect=1``, i.e. same scaling for x and y.
- - *float*: The displayed size of 1 unit in y-data coordinates will
- be *aspect* times the displayed size of 1 unit in x-data
- coordinates; e.g. for ``aspect=2`` a square in data coordinates
- will be rendered with a height of twice its width.
- adjustable : None or {'box', 'datalim'}, optional
- If not ``None``, this defines which parameter will be adjusted to
- meet the required aspect. See `.set_adjustable` for further
- details.
- anchor : None or str or (float, float), optional
- If not ``None``, this defines where the Axes will be drawn if there
- is extra space due to aspect constraints. The most common way
- to specify the anchor are abbreviations of cardinal directions:
- ===== =====================
- value description
- ===== =====================
- 'C' centered
- 'SW' lower left corner
- 'S' middle of bottom edge
- 'SE' lower right corner
- etc.
- ===== =====================
- See `~.Axes.set_anchor` for further details.
- share : bool, default: False
- If ``True``, apply the settings to all shared Axes.
- See Also
- --------
- matplotlib.axes.Axes.set_adjustable
- Set how the Axes adjusts to achieve the required aspect ratio.
- matplotlib.axes.Axes.set_anchor
- Set the position in case of extra space.
- """
- if cbook._str_equal(aspect, 'equal'):
- aspect = 1
- if not cbook._str_equal(aspect, 'auto'):
- aspect = float(aspect) # raise ValueError if necessary
- if aspect <= 0 or not np.isfinite(aspect):
- raise ValueError("aspect must be finite and positive ")
- if share:
- axes = {sibling for name in self._axis_names
- for sibling in self._shared_axes[name].get_siblings(self)}
- else:
- axes = [self]
- for ax in axes:
- ax._aspect = aspect
- if adjustable is None:
- adjustable = self._adjustable
- self.set_adjustable(adjustable, share=share) # Handle sharing.
- if anchor is not None:
- self.set_anchor(anchor, share=share)
- self.stale = True
- def get_adjustable(self):
- """
- Return whether the Axes will adjust its physical dimension ('box') or
- its data limits ('datalim') to achieve the desired aspect ratio.
- See Also
- --------
- matplotlib.axes.Axes.set_adjustable
- Set how the Axes adjusts to achieve the required aspect ratio.
- matplotlib.axes.Axes.set_aspect
- For a description of aspect handling.
- """
- return self._adjustable
- def set_adjustable(self, adjustable, share=False):
- """
- Set how the Axes adjusts to achieve the required aspect ratio.
- Parameters
- ----------
- adjustable : {'box', 'datalim'}
- If 'box', change the physical dimensions of the Axes.
- If 'datalim', change the ``x`` or ``y`` data limits.
- share : bool, default: False
- If ``True``, apply the settings to all shared Axes.
- See Also
- --------
- matplotlib.axes.Axes.set_aspect
- For a description of aspect handling.
- Notes
- -----
- Shared Axes (of which twinned Axes are a special case)
- impose restrictions on how aspect ratios can be imposed.
- For twinned Axes, use 'datalim'. For Axes that share both
- x and y, use 'box'. Otherwise, either 'datalim' or 'box'
- may be used. These limitations are partly a requirement
- to avoid over-specification, and partly a result of the
- particular implementation we are currently using, in
- which the adjustments for aspect ratios are done sequentially
- and independently on each Axes as it is drawn.
- """
- _api.check_in_list(["box", "datalim"], adjustable=adjustable)
- if share:
- axs = {sibling for name in self._axis_names
- for sibling in self._shared_axes[name].get_siblings(self)}
- else:
- axs = [self]
- if (adjustable == "datalim"
- and any(getattr(ax.get_data_ratio, "__func__", None)
- != _AxesBase.get_data_ratio
- for ax in axs)):
- # Limits adjustment by apply_aspect assumes that the axes' aspect
- # ratio can be computed from the data limits and scales.
- raise ValueError("Cannot set Axes adjustable to 'datalim' for "
- "Axes which override 'get_data_ratio'")
- for ax in axs:
- ax._adjustable = adjustable
- self.stale = True
- def get_box_aspect(self):
- """
- Return the Axes box aspect, i.e. the ratio of height to width.
- The box aspect is ``None`` (i.e. chosen depending on the available
- figure space) unless explicitly specified.
- See Also
- --------
- matplotlib.axes.Axes.set_box_aspect
- for a description of box aspect.
- matplotlib.axes.Axes.set_aspect
- for a description of aspect handling.
- """
- return self._box_aspect
- def set_box_aspect(self, aspect=None):
- """
- Set the Axes box aspect, i.e. the ratio of height to width.
- This defines the aspect of the Axes in figure space and is not to be
- confused with the data aspect (see `~.Axes.set_aspect`).
- Parameters
- ----------
- aspect : float or None
- Changes the physical dimensions of the Axes, such that the ratio
- of the Axes height to the Axes width in physical units is equal to
- *aspect*. Defining a box aspect will change the *adjustable*
- property to 'datalim' (see `~.Axes.set_adjustable`).
- *None* will disable a fixed box aspect so that height and width
- of the Axes are chosen independently.
- See Also
- --------
- matplotlib.axes.Axes.set_aspect
- for a description of aspect handling.
- """
- axs = {*self._twinned_axes.get_siblings(self),
- *self._twinned_axes.get_siblings(self)}
- if aspect is not None:
- aspect = float(aspect)
- # when box_aspect is set to other than ´None`,
- # adjustable must be "datalim"
- for ax in axs:
- ax.set_adjustable("datalim")
- for ax in axs:
- ax._box_aspect = aspect
- ax.stale = True
- def get_anchor(self):
- """
- Get the anchor location.
- See Also
- --------
- matplotlib.axes.Axes.set_anchor
- for a description of the anchor.
- matplotlib.axes.Axes.set_aspect
- for a description of aspect handling.
- """
- return self._anchor
- def set_anchor(self, anchor, share=False):
- """
- Define the anchor location.
- The actual drawing area (active position) of the Axes may be smaller
- than the Bbox (original position) when a fixed aspect is required. The
- anchor defines where the drawing area will be located within the
- available space.
- Parameters
- ----------
- anchor : (float, float) or {'C', 'SW', 'S', 'SE', 'E', 'NE', ...}
- Either an (*x*, *y*) pair of relative coordinates (0 is left or
- bottom, 1 is right or top), 'C' (center), or a cardinal direction
- ('SW', southwest, is bottom left, etc.). str inputs are shorthands
- for (*x*, *y*) coordinates, as shown in the following diagram::
- ┌─────────────────┬─────────────────┬─────────────────┐
- │ 'NW' (0.0, 1.0) │ 'N' (0.5, 1.0) │ 'NE' (1.0, 1.0) │
- ├─────────────────┼─────────────────┼─────────────────┤
- │ 'W' (0.0, 0.5) │ 'C' (0.5, 0.5) │ 'E' (1.0, 0.5) │
- ├─────────────────┼─────────────────┼─────────────────┤
- │ 'SW' (0.0, 0.0) │ 'S' (0.5, 0.0) │ 'SE' (1.0, 0.0) │
- └─────────────────┴─────────────────┴─────────────────┘
- share : bool, default: False
- If ``True``, apply the settings to all shared Axes.
- See Also
- --------
- matplotlib.axes.Axes.set_aspect
- for a description of aspect handling.
- """
- if not (anchor in mtransforms.Bbox.coefs or len(anchor) == 2):
- raise ValueError('argument must be among %s' %
- ', '.join(mtransforms.Bbox.coefs))
- if share:
- axes = {sibling for name in self._axis_names
- for sibling in self._shared_axes[name].get_siblings(self)}
- else:
- axes = [self]
- for ax in axes:
- ax._anchor = anchor
- self.stale = True
- def get_data_ratio(self):
- """
- Return the aspect ratio of the scaled data.
- Notes
- -----
- This method is intended to be overridden by new projection types.
- """
- txmin, txmax = self.xaxis.get_transform().transform(self.get_xbound())
- tymin, tymax = self.yaxis.get_transform().transform(self.get_ybound())
- xsize = max(abs(txmax - txmin), 1e-30)
- ysize = max(abs(tymax - tymin), 1e-30)
- return ysize / xsize
- def apply_aspect(self, position=None):
- """
- Adjust the Axes for a specified data aspect ratio.
- Depending on `.get_adjustable` this will modify either the
- Axes box (position) or the view limits. In the former case,
- `~matplotlib.axes.Axes.get_anchor` will affect the position.
- Parameters
- ----------
- position : None or .Bbox
- If not ``None``, this defines the position of the
- Axes within the figure as a Bbox. See `~.Axes.get_position`
- for further details.
- Notes
- -----
- This is called automatically when each Axes is drawn. You may need
- to call it yourself if you need to update the Axes position and/or
- view limits before the Figure is drawn.
- See Also
- --------
- matplotlib.axes.Axes.set_aspect
- For a description of aspect ratio handling.
- matplotlib.axes.Axes.set_adjustable
- Set how the Axes adjusts to achieve the required aspect ratio.
- matplotlib.axes.Axes.set_anchor
- Set the position in case of extra space.
- """
- if position is None:
- position = self.get_position(original=True)
- aspect = self.get_aspect()
- if aspect == 'auto' and self._box_aspect is None:
- self._set_position(position, which='active')
- return
- trans = self.get_figure().transSubfigure
- bb = mtransforms.Bbox.unit().transformed(trans)
- # this is the physical aspect of the panel (or figure):
- fig_aspect = bb.height / bb.width
- if self._adjustable == 'box':
- if self in self._twinned_axes:
- raise RuntimeError("Adjustable 'box' is not allowed in a "
- "twinned Axes; use 'datalim' instead")
- box_aspect = aspect * self.get_data_ratio()
- pb = position.frozen()
- pb1 = pb.shrunk_to_aspect(box_aspect, pb, fig_aspect)
- self._set_position(pb1.anchored(self.get_anchor(), pb), 'active')
- return
- # The following is only seen if self._adjustable == 'datalim'
- if self._box_aspect is not None:
- pb = position.frozen()
- pb1 = pb.shrunk_to_aspect(self._box_aspect, pb, fig_aspect)
- self._set_position(pb1.anchored(self.get_anchor(), pb), 'active')
- if aspect == "auto":
- return
- # reset active to original in case it had been changed by prior use
- # of 'box'
- if self._box_aspect is None:
- self._set_position(position, which='active')
- else:
- position = pb1.anchored(self.get_anchor(), pb)
- x_trf = self.xaxis.get_transform()
- y_trf = self.yaxis.get_transform()
- xmin, xmax = x_trf.transform(self.get_xbound())
- ymin, ymax = y_trf.transform(self.get_ybound())
- xsize = max(abs(xmax - xmin), 1e-30)
- ysize = max(abs(ymax - ymin), 1e-30)
- box_aspect = fig_aspect * (position.height / position.width)
- data_ratio = box_aspect / aspect
- y_expander = data_ratio * xsize / ysize - 1
- # If y_expander > 0, the dy/dx viewLim ratio needs to increase
- if abs(y_expander) < 0.005:
- return
- dL = self.dataLim
- x0, x1 = x_trf.transform(dL.intervalx)
- y0, y1 = y_trf.transform(dL.intervaly)
- xr = 1.05 * (x1 - x0)
- yr = 1.05 * (y1 - y0)
- xmarg = xsize - xr
- ymarg = ysize - yr
- Ysize = data_ratio * xsize
- Xsize = ysize / data_ratio
- Xmarg = Xsize - xr
- Ymarg = Ysize - yr
- # Setting these targets to, e.g., 0.05*xr does not seem to help.
- xm = 0
- ym = 0
- shared_x = self in self._shared_axes["x"]
- shared_y = self in self._shared_axes["y"]
- if shared_x and shared_y:
- raise RuntimeError("set_aspect(..., adjustable='datalim') or "
- "axis('equal') are not allowed when both axes "
- "are shared. Try set_aspect(..., "
- "adjustable='box').")
- # If y is shared, then we are only allowed to change x, etc.
- if shared_y:
- adjust_y = False
- else:
- if xmarg > xm and ymarg > ym:
- adjy = ((Ymarg > 0 and y_expander < 0) or
- (Xmarg < 0 and y_expander > 0))
- else:
- adjy = y_expander > 0
- adjust_y = shared_x or adjy # (Ymarg > xmarg)
- if adjust_y:
- yc = 0.5 * (ymin + ymax)
- y0 = yc - Ysize / 2.0
- y1 = yc + Ysize / 2.0
- self.set_ybound(y_trf.inverted().transform([y0, y1]))
- else:
- xc = 0.5 * (xmin + xmax)
- x0 = xc - Xsize / 2.0
- x1 = xc + Xsize / 2.0
- self.set_xbound(x_trf.inverted().transform([x0, x1]))
- def axis(self, arg=None, /, *, emit=True, **kwargs):
- """
- Convenience method to get or set some axis properties.
- Call signatures::
- xmin, xmax, ymin, ymax = axis()
- xmin, xmax, ymin, ymax = axis([xmin, xmax, ymin, ymax])
- xmin, xmax, ymin, ymax = axis(option)
- xmin, xmax, ymin, ymax = axis(**kwargs)
- Parameters
- ----------
- xmin, xmax, ymin, ymax : float, optional
- The axis limits to be set. This can also be achieved using ::
- ax.set(xlim=(xmin, xmax), ylim=(ymin, ymax))
- option : bool or str
- If a bool, turns axis lines and labels on or off. If a string,
- possible values are:
- ================ ===========================================================
- Value Description
- ================ ===========================================================
- 'off' or `False` Hide all axis decorations, i.e. axis labels, spines,
- tick marks, tick labels, and grid lines.
- This is the same as `~.Axes.set_axis_off()`.
- 'on' or `True` Do not hide all axis decorations, i.e. axis labels, spines,
- tick marks, tick labels, and grid lines.
- This is the same as `~.Axes.set_axis_on()`.
- 'equal' Set equal scaling (i.e., make circles circular) by
- changing the axis limits. This is the same as
- ``ax.set_aspect('equal', adjustable='datalim')``.
- Explicit data limits may not be respected in this case.
- 'scaled' Set equal scaling (i.e., make circles circular) by
- changing dimensions of the plot box. This is the same as
- ``ax.set_aspect('equal', adjustable='box', anchor='C')``.
- Additionally, further autoscaling will be disabled.
- 'tight' Set limits just large enough to show all data, then
- disable further autoscaling.
- 'auto' Automatic scaling (fill plot box with data).
- 'image' 'scaled' with axis limits equal to data limits.
- 'square' Square plot; similar to 'scaled', but initially forcing
- ``xmax-xmin == ymax-ymin``.
- ================ ===========================================================
- emit : bool, default: True
- Whether observers are notified of the axis limit change.
- This option is passed on to `~.Axes.set_xlim` and
- `~.Axes.set_ylim`.
- Returns
- -------
- xmin, xmax, ymin, ymax : float
- The axis limits.
- See Also
- --------
- matplotlib.axes.Axes.set_xlim
- matplotlib.axes.Axes.set_ylim
- Notes
- -----
- For 3D axes, this method additionally takes *zmin*, *zmax* as
- parameters and likewise returns them.
- """
- if isinstance(arg, (str, bool)):
- if arg is True:
- arg = 'on'
- if arg is False:
- arg = 'off'
- arg = arg.lower()
- if arg == 'on':
- self.set_axis_on()
- elif arg == 'off':
- self.set_axis_off()
- elif arg in [
- 'equal', 'tight', 'scaled', 'auto', 'image', 'square']:
- self.set_autoscale_on(True)
- self.set_aspect('auto')
- self.autoscale_view(tight=False)
- if arg == 'equal':
- self.set_aspect('equal', adjustable='datalim')
- elif arg == 'scaled':
- self.set_aspect('equal', adjustable='box', anchor='C')
- self.set_autoscale_on(False) # Req. by Mark Bakker
- elif arg == 'tight':
- self.autoscale_view(tight=True)
- self.set_autoscale_on(False)
- elif arg == 'image':
- self.autoscale_view(tight=True)
- self.set_autoscale_on(False)
- self.set_aspect('equal', adjustable='box', anchor='C')
- elif arg == 'square':
- self.set_aspect('equal', adjustable='box', anchor='C')
- self.set_autoscale_on(False)
- xlim = self.get_xlim()
- ylim = self.get_ylim()
- edge_size = max(np.diff(xlim), np.diff(ylim))[0]
- self.set_xlim([xlim[0], xlim[0] + edge_size],
- emit=emit, auto=False)
- self.set_ylim([ylim[0], ylim[0] + edge_size],
- emit=emit, auto=False)
- else:
- raise ValueError(f"Unrecognized string {arg!r} to axis; "
- "try 'on' or 'off'")
- else:
- if arg is not None:
- if len(arg) != 2*len(self._axis_names):
- raise TypeError(
- "The first argument to axis() must be an iterable of the form "
- "[{}]".format(", ".join(
- f"{name}min, {name}max" for name in self._axis_names)))
- limits = {
- name: arg[2*i:2*(i+1)]
- for i, name in enumerate(self._axis_names)
- }
- else:
- limits = {}
- for name in self._axis_names:
- ax_min = kwargs.pop(f'{name}min', None)
- ax_max = kwargs.pop(f'{name}max', None)
- limits[name] = (ax_min, ax_max)
- for name, (ax_min, ax_max) in limits.items():
- ax_auto = (None # Keep autoscale state as is.
- if ax_min is None and ax_max is None
- else False) # Turn off autoscale.
- set_ax_lim = getattr(self, f'set_{name}lim')
- set_ax_lim(ax_min, ax_max, emit=emit, auto=ax_auto)
- if kwargs:
- raise _api.kwarg_error("axis", kwargs)
- lims = ()
- for name in self._axis_names:
- get_ax_lim = getattr(self, f'get_{name}lim')
- lims += get_ax_lim()
- return lims
- def get_legend(self):
- """Return the `.Legend` instance, or None if no legend is defined."""
- return self.legend_
- def get_images(self):
- r"""Return a list of `.AxesImage`\s contained by the Axes."""
- return cbook.silent_list('AxesImage', self.images)
- def get_lines(self):
- """Return a list of lines contained by the Axes."""
- return cbook.silent_list('Line2D', self.lines)
- def get_xaxis(self):
- """
- [*Discouraged*] Return the XAxis instance.
- .. admonition:: Discouraged
- The use of this function is discouraged. You should instead
- directly access the attribute ``ax.xaxis``.
- """
- return self.xaxis
- def get_yaxis(self):
- """
- [*Discouraged*] Return the YAxis instance.
- .. admonition:: Discouraged
- The use of this function is discouraged. You should instead
- directly access the attribute ``ax.yaxis``.
- """
- return self.yaxis
- get_xgridlines = _axis_method_wrapper("xaxis", "get_gridlines")
- get_xticklines = _axis_method_wrapper("xaxis", "get_ticklines")
- get_ygridlines = _axis_method_wrapper("yaxis", "get_gridlines")
- get_yticklines = _axis_method_wrapper("yaxis", "get_ticklines")
- # Adding and tracking artists
- def _sci(self, im):
- """
- Set the current image.
- This image will be the target of colormap functions like
- ``pyplot.viridis``, and other functions such as `~.pyplot.clim`. The
- current image is an attribute of the current Axes.
- """
- _api.check_isinstance((mcoll.Collection, mimage.AxesImage), im=im)
- if im not in self._children:
- raise ValueError("Argument must be an image or collection in this Axes")
- self._current_image = im
- def _gci(self):
- """Helper for `~matplotlib.pyplot.gci`; do not use elsewhere."""
- return self._current_image
- def has_data(self):
- """
- Return whether any artists have been added to the Axes.
- This should not be used to determine whether the *dataLim*
- need to be updated, and may not actually be useful for
- anything.
- """
- return any(isinstance(a, (mcoll.Collection, mimage.AxesImage,
- mlines.Line2D, mpatches.Patch))
- for a in self._children)
- def add_artist(self, a):
- """
- Add an `.Artist` to the Axes; return the artist.
- Use `add_artist` only for artists for which there is no dedicated
- "add" method; and if necessary, use a method such as `update_datalim`
- to manually update the dataLim if the artist is to be included in
- autoscaling.
- If no ``transform`` has been specified when creating the artist (e.g.
- ``artist.get_transform() == None``) then the transform is set to
- ``ax.transData``.
- """
- a.axes = self
- self._children.append(a)
- a._remove_method = self._children.remove
- self._set_artist_props(a)
- if a.get_clip_path() is None:
- a.set_clip_path(self.patch)
- self.stale = True
- return a
- def add_child_axes(self, ax):
- """
- Add an `.AxesBase` to the Axes' children; return the child Axes.
- This is the lowlevel version. See `.axes.Axes.inset_axes`.
- """
- # normally Axes have themselves as the Axes, but these need to have
- # their parent...
- # Need to bypass the getter...
- ax._axes = self
- ax.stale_callback = martist._stale_axes_callback
- self.child_axes.append(ax)
- ax._remove_method = functools.partial(
- self.figure._remove_axes, owners=[self.child_axes])
- self.stale = True
- return ax
- def add_collection(self, collection, autolim=True):
- """
- Add a `.Collection` to the Axes; return the collection.
- """
- _api.check_isinstance(mcoll.Collection, collection=collection)
- if not collection.get_label():
- collection.set_label(f'_child{len(self._children)}')
- self._children.append(collection)
- collection._remove_method = self._children.remove
- self._set_artist_props(collection)
- if collection.get_clip_path() is None:
- collection.set_clip_path(self.patch)
- if autolim:
- # Make sure viewLim is not stale (mostly to match
- # pre-lazy-autoscale behavior, which is not really better).
- self._unstale_viewLim()
- datalim = collection.get_datalim(self.transData)
- points = datalim.get_points()
- if not np.isinf(datalim.minpos).all():
- # By definition, if minpos (minimum positive value) is set
- # (i.e., non-inf), then min(points) <= minpos <= max(points),
- # and minpos would be superfluous. However, we add minpos to
- # the call so that self.dataLim will update its own minpos.
- # This ensures that log scales see the correct minimum.
- points = np.concatenate([points, [datalim.minpos]])
- self.update_datalim(points)
- self.stale = True
- return collection
- def add_image(self, image):
- """
- Add an `.AxesImage` to the Axes; return the image.
- """
- _api.check_isinstance(mimage.AxesImage, image=image)
- self._set_artist_props(image)
- if not image.get_label():
- image.set_label(f'_child{len(self._children)}')
- self._children.append(image)
- image._remove_method = self._children.remove
- self.stale = True
- return image
- def _update_image_limits(self, image):
- xmin, xmax, ymin, ymax = image.get_extent()
- self.axes.update_datalim(((xmin, ymin), (xmax, ymax)))
- def add_line(self, line):
- """
- Add a `.Line2D` to the Axes; return the line.
- """
- _api.check_isinstance(mlines.Line2D, line=line)
- self._set_artist_props(line)
- if line.get_clip_path() is None:
- line.set_clip_path(self.patch)
- self._update_line_limits(line)
- if not line.get_label():
- line.set_label(f'_child{len(self._children)}')
- self._children.append(line)
- line._remove_method = self._children.remove
- self.stale = True
- return line
- def _add_text(self, txt):
- """
- Add a `.Text` to the Axes; return the text.
- """
- _api.check_isinstance(mtext.Text, txt=txt)
- self._set_artist_props(txt)
- self._children.append(txt)
- txt._remove_method = self._children.remove
- self.stale = True
- return txt
- def _update_line_limits(self, line):
- """
- Figures out the data limit of the given line, updating self.dataLim.
- """
- path = line.get_path()
- if path.vertices.size == 0:
- return
- line_trf = line.get_transform()
- if line_trf == self.transData:
- data_path = path
- elif any(line_trf.contains_branch_seperately(self.transData)):
- # Compute the transform from line coordinates to data coordinates.
- trf_to_data = line_trf - self.transData
- # If transData is affine we can use the cached non-affine component
- # of line's path (since the non-affine part of line_trf is
- # entirely encapsulated in trf_to_data).
- if self.transData.is_affine:
- line_trans_path = line._get_transformed_path()
- na_path, _ = line_trans_path.get_transformed_path_and_affine()
- data_path = trf_to_data.transform_path_affine(na_path)
- else:
- data_path = trf_to_data.transform_path(path)
- else:
- # For backwards compatibility we update the dataLim with the
- # coordinate range of the given path, even though the coordinate
- # systems are completely different. This may occur in situations
- # such as when ax.transAxes is passed through for absolute
- # positioning.
- data_path = path
- if not data_path.vertices.size:
- return
- updatex, updatey = line_trf.contains_branch_seperately(self.transData)
- if self.name != "rectilinear":
- # This block is mostly intended to handle axvline in polar plots,
- # for which updatey would otherwise be True.
- if updatex and line_trf == self.get_yaxis_transform():
- updatex = False
- if updatey and line_trf == self.get_xaxis_transform():
- updatey = False
- self.dataLim.update_from_path(data_path,
- self.ignore_existing_data_limits,
- updatex=updatex, updatey=updatey)
- self.ignore_existing_data_limits = False
- def add_patch(self, p):
- """
- Add a `.Patch` to the Axes; return the patch.
- """
- _api.check_isinstance(mpatches.Patch, p=p)
- self._set_artist_props(p)
- if p.get_clip_path() is None:
- p.set_clip_path(self.patch)
- self._update_patch_limits(p)
- self._children.append(p)
- p._remove_method = self._children.remove
- return p
- def _update_patch_limits(self, patch):
- """Update the data limits for the given patch."""
- # hist can add zero height Rectangles, which is useful to keep
- # the bins, counts and patches lined up, but it throws off log
- # scaling. We'll ignore rects with zero height or width in
- # the auto-scaling
- # cannot check for '==0' since unitized data may not compare to zero
- # issue #2150 - we update the limits if patch has non zero width
- # or height.
- if (isinstance(patch, mpatches.Rectangle) and
- ((not patch.get_width()) and (not patch.get_height()))):
- return
- p = patch.get_path()
- # Get all vertices on the path
- # Loop through each segment to get extrema for Bezier curve sections
- vertices = []
- for curve, code in p.iter_bezier(simplify=False):
- # Get distance along the curve of any extrema
- _, dzeros = curve.axis_aligned_extrema()
- # Calculate vertices of start, end and any extrema in between
- vertices.append(curve([0, *dzeros, 1]))
- if len(vertices):
- vertices = np.vstack(vertices)
- patch_trf = patch.get_transform()
- updatex, updatey = patch_trf.contains_branch_seperately(self.transData)
- if not (updatex or updatey):
- return
- if self.name != "rectilinear":
- # As in _update_line_limits, but for axvspan.
- if updatex and patch_trf == self.get_yaxis_transform():
- updatex = False
- if updatey and patch_trf == self.get_xaxis_transform():
- updatey = False
- trf_to_data = patch_trf - self.transData
- xys = trf_to_data.transform(vertices)
- self.update_datalim(xys, updatex=updatex, updatey=updatey)
- def add_table(self, tab):
- """
- Add a `.Table` to the Axes; return the table.
- """
- _api.check_isinstance(mtable.Table, tab=tab)
- self._set_artist_props(tab)
- self._children.append(tab)
- if tab.get_clip_path() is None:
- tab.set_clip_path(self.patch)
- tab._remove_method = self._children.remove
- return tab
- def add_container(self, container):
- """
- Add a `.Container` to the Axes' containers; return the container.
- """
- label = container.get_label()
- if not label:
- container.set_label('_container%d' % len(self.containers))
- self.containers.append(container)
- container._remove_method = self.containers.remove
- return container
- def _unit_change_handler(self, axis_name, event=None):
- """
- Process axis units changes: requests updates to data and view limits.
- """
- if event is None: # Allow connecting `self._unit_change_handler(name)`
- return functools.partial(
- self._unit_change_handler, axis_name, event=object())
- _api.check_in_list(self._axis_map, axis_name=axis_name)
- for line in self.lines:
- line.recache_always()
- self.relim()
- self._request_autoscale_view(axis_name)
- def relim(self, visible_only=False):
- """
- Recompute the data limits based on current artists.
- At present, `.Collection` instances are not supported.
- Parameters
- ----------
- visible_only : bool, default: False
- Whether to exclude invisible artists.
- """
- # Collections are deliberately not supported (yet); see
- # the TODO note in artists.py.
- self.dataLim.ignore(True)
- self.dataLim.set_points(mtransforms.Bbox.null().get_points())
- self.ignore_existing_data_limits = True
- for artist in self._children:
- if not visible_only or artist.get_visible():
- if isinstance(artist, mlines.Line2D):
- self._update_line_limits(artist)
- elif isinstance(artist, mpatches.Patch):
- self._update_patch_limits(artist)
- elif isinstance(artist, mimage.AxesImage):
- self._update_image_limits(artist)
- def update_datalim(self, xys, updatex=True, updatey=True):
- """
- Extend the `~.Axes.dataLim` Bbox to include the given points.
- If no data is set currently, the Bbox will ignore its limits and set
- the bound to be the bounds of the xydata (*xys*). Otherwise, it will
- compute the bounds of the union of its current data and the data in
- *xys*.
- Parameters
- ----------
- xys : 2D array-like
- The points to include in the data limits Bbox. This can be either
- a list of (x, y) tuples or a (N, 2) array.
- updatex, updatey : bool, default: True
- Whether to update the x/y limits.
- """
- xys = np.asarray(xys)
- if not np.any(np.isfinite(xys)):
- return
- self.dataLim.update_from_data_xy(xys, self.ignore_existing_data_limits,
- updatex=updatex, updatey=updatey)
- self.ignore_existing_data_limits = False
- def _process_unit_info(self, datasets=None, kwargs=None, *, convert=True):
- """
- Set axis units based on *datasets* and *kwargs*, and optionally apply
- unit conversions to *datasets*.
- Parameters
- ----------
- datasets : list
- List of (axis_name, dataset) pairs (where the axis name is defined
- as in `._axis_map`). Individual datasets can also be None
- (which gets passed through).
- kwargs : dict
- Other parameters from which unit info (i.e., the *xunits*,
- *yunits*, *zunits* (for 3D Axes), *runits* and *thetaunits* (for
- polar) entries) is popped, if present. Note that this dict is
- mutated in-place!
- convert : bool, default: True
- Whether to return the original datasets or the converted ones.
- Returns
- -------
- list
- Either the original datasets if *convert* is False, or the
- converted ones if *convert* is True (the default).
- """
- # The API makes datasets a list of pairs rather than an axis_name to
- # dataset mapping because it is sometimes necessary to process multiple
- # datasets for a single axis, and concatenating them may be tricky
- # (e.g. if some are scalars, etc.).
- datasets = datasets or []
- kwargs = kwargs or {}
- axis_map = self._axis_map
- for axis_name, data in datasets:
- try:
- axis = axis_map[axis_name]
- except KeyError:
- raise ValueError(f"Invalid axis name: {axis_name!r}") from None
- # Update from data if axis is already set but no unit is set yet.
- if axis is not None and data is not None and not axis.have_units():
- axis.update_units(data)
- for axis_name, axis in axis_map.items():
- # Return if no axis is set.
- if axis is None:
- continue
- # Check for units in the kwargs, and if present update axis.
- units = kwargs.pop(f"{axis_name}units", axis.units)
- if self.name == "polar":
- # Special case: polar supports "thetaunits"/"runits".
- polar_units = {"x": "thetaunits", "y": "runits"}
- units = kwargs.pop(polar_units[axis_name], units)
- if units != axis.units and units is not None:
- axis.set_units(units)
- # If the units being set imply a different converter,
- # we need to update again.
- for dataset_axis_name, data in datasets:
- if dataset_axis_name == axis_name and data is not None:
- axis.update_units(data)
- return [axis_map[axis_name].convert_units(data)
- if convert and data is not None else data
- for axis_name, data in datasets]
- def in_axes(self, mouseevent):
- """
- Return whether the given event (in display coords) is in the Axes.
- """
- return self.patch.contains(mouseevent)[0]
- get_autoscalex_on = _axis_method_wrapper("xaxis", "_get_autoscale_on")
- get_autoscaley_on = _axis_method_wrapper("yaxis", "_get_autoscale_on")
- set_autoscalex_on = _axis_method_wrapper("xaxis", "_set_autoscale_on")
- set_autoscaley_on = _axis_method_wrapper("yaxis", "_set_autoscale_on")
- def get_autoscale_on(self):
- """Return True if each axis is autoscaled, False otherwise."""
- return all(axis._get_autoscale_on()
- for axis in self._axis_map.values())
- def set_autoscale_on(self, b):
- """
- Set whether autoscaling is applied to each axis on the next draw or
- call to `.Axes.autoscale_view`.
- Parameters
- ----------
- b : bool
- """
- for axis in self._axis_map.values():
- axis._set_autoscale_on(b)
- @property
- def use_sticky_edges(self):
- """
- When autoscaling, whether to obey all `Artist.sticky_edges`.
- Default is ``True``.
- Setting this to ``False`` ensures that the specified margins
- will be applied, even if the plot includes an image, for
- example, which would otherwise force a view limit to coincide
- with its data limit.
- The changing this property does not change the plot until
- `autoscale` or `autoscale_view` is called.
- """
- return self._use_sticky_edges
- @use_sticky_edges.setter
- def use_sticky_edges(self, b):
- self._use_sticky_edges = bool(b)
- # No effect until next autoscaling, which will mark the Axes as stale.
- def set_xmargin(self, m):
- """
- Set padding of X data limits prior to autoscaling.
- *m* times the data interval will be added to each end of that interval
- before it is used in autoscaling. If *m* is negative, this will clip
- the data range instead of expanding it.
- For example, if your data is in the range [0, 2], a margin of 0.1 will
- result in a range [-0.2, 2.2]; a margin of -0.1 will result in a range
- of [0.2, 1.8].
- Parameters
- ----------
- m : float greater than -0.5
- """
- if m <= -0.5:
- raise ValueError("margin must be greater than -0.5")
- self._xmargin = m
- self._request_autoscale_view("x")
- self.stale = True
- def set_ymargin(self, m):
- """
- Set padding of Y data limits prior to autoscaling.
- *m* times the data interval will be added to each end of that interval
- before it is used in autoscaling. If *m* is negative, this will clip
- the data range instead of expanding it.
- For example, if your data is in the range [0, 2], a margin of 0.1 will
- result in a range [-0.2, 2.2]; a margin of -0.1 will result in a range
- of [0.2, 1.8].
- Parameters
- ----------
- m : float greater than -0.5
- """
- if m <= -0.5:
- raise ValueError("margin must be greater than -0.5")
- self._ymargin = m
- self._request_autoscale_view("y")
- self.stale = True
- def margins(self, *margins, x=None, y=None, tight=True):
- """
- Set or retrieve autoscaling margins.
- The padding added to each limit of the Axes is the *margin*
- times the data interval. All input parameters must be floats
- greater than -0.5. Passing both positional and keyword
- arguments is invalid and will raise a TypeError. If no
- arguments (positional or otherwise) are provided, the current
- margins will remain unchanged and simply be returned.
- Specifying any margin changes only the autoscaling; for example,
- if *xmargin* is not None, then *xmargin* times the X data
- interval will be added to each end of that interval before
- it is used in autoscaling.
- Parameters
- ----------
- *margins : float, optional
- If a single positional argument is provided, it specifies
- both margins of the x-axis and y-axis limits. If two
- positional arguments are provided, they will be interpreted
- as *xmargin*, *ymargin*. If setting the margin on a single
- axis is desired, use the keyword arguments described below.
- x, y : float, optional
- Specific margin values for the x-axis and y-axis,
- respectively. These cannot be used with positional
- arguments, but can be used individually to alter on e.g.,
- only the y-axis.
- tight : bool or None, default: True
- The *tight* parameter is passed to `~.axes.Axes.autoscale_view`,
- which is executed after a margin is changed; the default
- here is *True*, on the assumption that when margins are
- specified, no additional padding to match tick marks is
- usually desired. Setting *tight* to *None* preserves
- the previous setting.
- Returns
- -------
- xmargin, ymargin : float
- Notes
- -----
- If a previously used Axes method such as :meth:`pcolor` has set
- :attr:`use_sticky_edges` to `True`, only the limits not set by
- the "sticky artists" will be modified. To force all of the
- margins to be set, set :attr:`use_sticky_edges` to `False`
- before calling :meth:`margins`.
- """
- if margins and (x is not None or y is not None):
- raise TypeError('Cannot pass both positional and keyword '
- 'arguments for x and/or y.')
- elif len(margins) == 1:
- x = y = margins[0]
- elif len(margins) == 2:
- x, y = margins
- elif margins:
- raise TypeError('Must pass a single positional argument for all '
- 'margins, or one for each margin (x, y).')
- if x is None and y is None:
- if tight is not True:
- _api.warn_external(f'ignoring tight={tight!r} in get mode')
- return self._xmargin, self._ymargin
- if tight is not None:
- self._tight = tight
- if x is not None:
- self.set_xmargin(x)
- if y is not None:
- self.set_ymargin(y)
- def set_rasterization_zorder(self, z):
- """
- Set the zorder threshold for rasterization for vector graphics output.
- All artists with a zorder below the given value will be rasterized if
- they support rasterization.
- This setting is ignored for pixel-based output.
- See also :doc:`/gallery/misc/rasterization_demo`.
- Parameters
- ----------
- z : float or None
- The zorder below which artists are rasterized.
- If ``None`` rasterization based on zorder is deactivated.
- """
- self._rasterization_zorder = z
- self.stale = True
- def get_rasterization_zorder(self):
- """Return the zorder value below which artists will be rasterized."""
- return self._rasterization_zorder
- def autoscale(self, enable=True, axis='both', tight=None):
- """
- Autoscale the axis view to the data (toggle).
- Convenience method for simple axis view autoscaling.
- It turns autoscaling on or off, and then,
- if autoscaling for either axis is on, it performs
- the autoscaling on the specified axis or Axes.
- Parameters
- ----------
- enable : bool or None, default: True
- True turns autoscaling on, False turns it off.
- None leaves the autoscaling state unchanged.
- axis : {'both', 'x', 'y'}, default: 'both'
- The axis on which to operate. (For 3D Axes, *axis* can also be set
- to 'z', and 'both' refers to all three axes.)
- tight : bool or None, default: None
- If True, first set the margins to zero. Then, this argument is
- forwarded to `~.axes.Axes.autoscale_view` (regardless of
- its value); see the description of its behavior there.
- """
- if enable is None:
- scalex = True
- scaley = True
- else:
- if axis in ['x', 'both']:
- self.set_autoscalex_on(bool(enable))
- scalex = self.get_autoscalex_on()
- else:
- scalex = False
- if axis in ['y', 'both']:
- self.set_autoscaley_on(bool(enable))
- scaley = self.get_autoscaley_on()
- else:
- scaley = False
- if tight and scalex:
- self._xmargin = 0
- if tight and scaley:
- self._ymargin = 0
- if scalex:
- self._request_autoscale_view("x", tight=tight)
- if scaley:
- self._request_autoscale_view("y", tight=tight)
- def autoscale_view(self, tight=None, scalex=True, scaley=True):
- """
- Autoscale the view limits using the data limits.
- Parameters
- ----------
- tight : bool or None
- If *True*, only expand the axis limits using the margins. Note
- that unlike for `autoscale`, ``tight=True`` does *not* set the
- margins to zero.
- If *False* and :rc:`axes.autolimit_mode` is 'round_numbers', then
- after expansion by the margins, further expand the axis limits
- using the axis major locator.
- If None (the default), reuse the value set in the previous call to
- `autoscale_view` (the initial value is False, but the default style
- sets :rc:`axes.autolimit_mode` to 'data', in which case this
- behaves like True).
- scalex : bool, default: True
- Whether to autoscale the x-axis.
- scaley : bool, default: True
- Whether to autoscale the y-axis.
- Notes
- -----
- The autoscaling preserves any preexisting axis direction reversal.
- The data limits are not updated automatically when artist data are
- changed after the artist has been added to an Axes instance. In that
- case, use :meth:`matplotlib.axes.Axes.relim` prior to calling
- autoscale_view.
- If the views of the Axes are fixed, e.g. via `set_xlim`, they will
- not be changed by autoscale_view().
- See :meth:`matplotlib.axes.Axes.autoscale` for an alternative.
- """
- if tight is not None:
- self._tight = bool(tight)
- x_stickies = y_stickies = np.array([])
- if self.use_sticky_edges:
- if self._xmargin and scalex and self.get_autoscalex_on():
- x_stickies = np.sort(np.concatenate([
- artist.sticky_edges.x
- for ax in self._shared_axes["x"].get_siblings(self)
- for artist in ax.get_children()]))
- if self._ymargin and scaley and self.get_autoscaley_on():
- y_stickies = np.sort(np.concatenate([
- artist.sticky_edges.y
- for ax in self._shared_axes["y"].get_siblings(self)
- for artist in ax.get_children()]))
- if self.get_xscale() == 'log':
- x_stickies = x_stickies[x_stickies > 0]
- if self.get_yscale() == 'log':
- y_stickies = y_stickies[y_stickies > 0]
- def handle_single_axis(
- scale, shared_axes, name, axis, margin, stickies, set_bound):
- if not (scale and axis._get_autoscale_on()):
- return # nothing to do...
- shared = shared_axes.get_siblings(self)
- # Base autoscaling on finite data limits when there is at least one
- # finite data limit among all the shared_axes and intervals.
- values = [val for ax in shared
- for val in getattr(ax.dataLim, f"interval{name}")
- if np.isfinite(val)]
- if values:
- x0, x1 = (min(values), max(values))
- elif getattr(self._viewLim, f"mutated{name}")():
- # No data, but explicit viewLims already set:
- # in mutatedx or mutatedy.
- return
- else:
- x0, x1 = (-np.inf, np.inf)
- # If x0 and x1 are nonfinite, get default limits from the locator.
- locator = axis.get_major_locator()
- x0, x1 = locator.nonsingular(x0, x1)
- # Find the minimum minpos for use in the margin calculation.
- minimum_minpos = min(
- getattr(ax.dataLim, f"minpos{name}") for ax in shared)
- # Prevent margin addition from crossing a sticky value. A small
- # tolerance must be added due to floating point issues with
- # streamplot; it is defined relative to x0, x1, x1-x0 but has
- # no absolute term (e.g. "+1e-8") to avoid issues when working with
- # datasets where all values are tiny (less than 1e-8).
- tol = 1e-5 * max(abs(x0), abs(x1), abs(x1 - x0))
- # Index of largest element < x0 + tol, if any.
- i0 = stickies.searchsorted(x0 + tol) - 1
- x0bound = stickies[i0] if i0 != -1 else None
- # Index of smallest element > x1 - tol, if any.
- i1 = stickies.searchsorted(x1 - tol)
- x1bound = stickies[i1] if i1 != len(stickies) else None
- # Add the margin in figure space and then transform back, to handle
- # non-linear scales.
- transform = axis.get_transform()
- inverse_trans = transform.inverted()
- x0, x1 = axis._scale.limit_range_for_scale(x0, x1, minimum_minpos)
- x0t, x1t = transform.transform([x0, x1])
- delta = (x1t - x0t) * margin
- if not np.isfinite(delta):
- delta = 0 # If a bound isn't finite, set margin to zero.
- x0, x1 = inverse_trans.transform([x0t - delta, x1t + delta])
- # Apply sticky bounds.
- if x0bound is not None:
- x0 = max(x0, x0bound)
- if x1bound is not None:
- x1 = min(x1, x1bound)
- if not self._tight:
- x0, x1 = locator.view_limits(x0, x1)
- set_bound(x0, x1)
- # End of definition of internal function 'handle_single_axis'.
- handle_single_axis(
- scalex, self._shared_axes["x"], 'x', self.xaxis, self._xmargin,
- x_stickies, self.set_xbound)
- handle_single_axis(
- scaley, self._shared_axes["y"], 'y', self.yaxis, self._ymargin,
- y_stickies, self.set_ybound)
- def _update_title_position(self, renderer):
- """
- Update the title position based on the bounding box enclosing
- all the ticklabels and x-axis spine and xlabel...
- """
- if self._autotitlepos is not None and not self._autotitlepos:
- _log.debug('title position was updated manually, not adjusting')
- return
- titles = (self.title, self._left_title, self._right_title)
- # Need to check all our twins too, and all the children as well.
- axs = self._twinned_axes.get_siblings(self) + self.child_axes
- for ax in self.child_axes: # Child positions must be updated first.
- locator = ax.get_axes_locator()
- ax.apply_aspect(locator(self, renderer) if locator else None)
- for title in titles:
- x, _ = title.get_position()
- # need to start again in case of window resizing
- title.set_position((x, 1.0))
- top = -np.inf
- for ax in axs:
- bb = None
- if (ax.xaxis.get_ticks_position() in ['top', 'unknown']
- or ax.xaxis.get_label_position() == 'top'):
- bb = ax.xaxis.get_tightbbox(renderer)
- if bb is None:
- if 'outline' in ax.spines:
- # Special case for colorbars:
- bb = ax.spines['outline'].get_window_extent()
- else:
- bb = ax.get_window_extent(renderer)
- top = max(top, bb.ymax)
- if title.get_text():
- ax.yaxis.get_tightbbox(renderer) # update offsetText
- if ax.yaxis.offsetText.get_text():
- bb = ax.yaxis.offsetText.get_tightbbox(renderer)
- if bb.intersection(title.get_tightbbox(renderer), bb):
- top = bb.ymax
- if top < 0:
- # the top of Axes is not even on the figure, so don't try and
- # automatically place it.
- _log.debug('top of Axes not in the figure, so title not moved')
- return
- if title.get_window_extent(renderer).ymin < top:
- _, y = self.transAxes.inverted().transform((0, top))
- title.set_position((x, y))
- # empirically, this doesn't always get the min to top,
- # so we need to adjust again.
- if title.get_window_extent(renderer).ymin < top:
- _, y = self.transAxes.inverted().transform(
- (0., 2 * top - title.get_window_extent(renderer).ymin))
- title.set_position((x, y))
- ymax = max(title.get_position()[1] for title in titles)
- for title in titles:
- # now line up all the titles at the highest baseline.
- x, _ = title.get_position()
- title.set_position((x, ymax))
- # Drawing
- @martist.allow_rasterization
- def draw(self, renderer):
- # docstring inherited
- if renderer is None:
- raise RuntimeError('No renderer defined')
- if not self.get_visible():
- return
- self._unstale_viewLim()
- renderer.open_group('axes', gid=self.get_gid())
- # prevent triggering call backs during the draw process
- self._stale = True
- # loop over self and child Axes...
- locator = self.get_axes_locator()
- self.apply_aspect(locator(self, renderer) if locator else None)
- artists = self.get_children()
- artists.remove(self.patch)
- # the frame draws the edges around the Axes patch -- we
- # decouple these so the patch can be in the background and the
- # frame in the foreground. Do this before drawing the axis
- # objects so that the spine has the opportunity to update them.
- if not (self.axison and self._frameon):
- for spine in self.spines.values():
- artists.remove(spine)
- self._update_title_position(renderer)
- if not self.axison:
- for _axis in self._axis_map.values():
- artists.remove(_axis)
- if not self.figure.canvas.is_saving():
- artists = [
- a for a in artists
- if not a.get_animated() or isinstance(a, mimage.AxesImage)]
- artists = sorted(artists, key=attrgetter('zorder'))
- # rasterize artists with negative zorder
- # if the minimum zorder is negative, start rasterization
- rasterization_zorder = self._rasterization_zorder
- if (rasterization_zorder is not None and
- artists and artists[0].zorder < rasterization_zorder):
- split_index = np.searchsorted(
- [art.zorder for art in artists],
- rasterization_zorder, side='right'
- )
- artists_rasterized = artists[:split_index]
- artists = artists[split_index:]
- else:
- artists_rasterized = []
- if self.axison and self._frameon:
- if artists_rasterized:
- artists_rasterized = [self.patch] + artists_rasterized
- else:
- artists = [self.patch] + artists
- if artists_rasterized:
- _draw_rasterized(self.figure, artists_rasterized, renderer)
- mimage._draw_list_compositing_images(
- renderer, self, artists, self.figure.suppressComposite)
- renderer.close_group('axes')
- self.stale = False
- def draw_artist(self, a):
- """
- Efficiently redraw a single artist.
- """
- a.draw(self.figure.canvas.get_renderer())
- def redraw_in_frame(self):
- """
- Efficiently redraw Axes data, but not axis ticks, labels, etc.
- """
- with ExitStack() as stack:
- for artist in [*self._axis_map.values(),
- self.title, self._left_title, self._right_title]:
- stack.enter_context(artist._cm_set(visible=False))
- self.draw(self.figure.canvas.get_renderer())
- # Axes rectangle characteristics
- def get_frame_on(self):
- """Get whether the Axes rectangle patch is drawn."""
- return self._frameon
- def set_frame_on(self, b):
- """
- Set whether the Axes rectangle patch is drawn.
- Parameters
- ----------
- b : bool
- """
- self._frameon = b
- self.stale = True
- def get_axisbelow(self):
- """
- Get whether axis ticks and gridlines are above or below most artists.
- Returns
- -------
- bool or 'line'
- See Also
- --------
- set_axisbelow
- """
- return self._axisbelow
- def set_axisbelow(self, b):
- """
- Set whether axis ticks and gridlines are above or below most artists.
- This controls the zorder of the ticks and gridlines. For more
- information on the zorder see :doc:`/gallery/misc/zorder_demo`.
- Parameters
- ----------
- b : bool or 'line'
- Possible values:
- - *True* (zorder = 0.5): Ticks and gridlines are below all Artists.
- - 'line' (zorder = 1.5): Ticks and gridlines are above patches
- (e.g. rectangles, with default zorder = 1) but still below lines
- and markers (with their default zorder = 2).
- - *False* (zorder = 2.5): Ticks and gridlines are above patches
- and lines / markers.
- See Also
- --------
- get_axisbelow
- """
- # Check that b is True, False or 'line'
- self._axisbelow = axisbelow = validate_axisbelow(b)
- zorder = {
- True: 0.5,
- 'line': 1.5,
- False: 2.5,
- }[axisbelow]
- for axis in self._axis_map.values():
- axis.set_zorder(zorder)
- self.stale = True
- @_docstring.dedent_interpd
- def grid(self, visible=None, which='major', axis='both', **kwargs):
- """
- Configure the grid lines.
- Parameters
- ----------
- visible : bool or None, optional
- Whether to show the grid lines. If any *kwargs* are supplied, it
- is assumed you want the grid on and *visible* will be set to True.
- If *visible* is *None* and there are no *kwargs*, this toggles the
- visibility of the lines.
- which : {'major', 'minor', 'both'}, optional
- The grid lines to apply the changes on.
- axis : {'both', 'x', 'y'}, optional
- The axis to apply the changes on.
- **kwargs : `~matplotlib.lines.Line2D` properties
- Define the line properties of the grid, e.g.::
- grid(color='r', linestyle='-', linewidth=2)
- Valid keyword arguments are:
- %(Line2D:kwdoc)s
- Notes
- -----
- The axis is drawn as a unit, so the effective zorder for drawing the
- grid is determined by the zorder of each axis, not by the zorder of the
- `.Line2D` objects comprising the grid. Therefore, to set grid zorder,
- use `.set_axisbelow` or, for more control, call the
- `~.Artist.set_zorder` method of each axis.
- """
- _api.check_in_list(['x', 'y', 'both'], axis=axis)
- if axis in ['x', 'both']:
- self.xaxis.grid(visible, which=which, **kwargs)
- if axis in ['y', 'both']:
- self.yaxis.grid(visible, which=which, **kwargs)
- def ticklabel_format(self, *, axis='both', style='', scilimits=None,
- useOffset=None, useLocale=None, useMathText=None):
- r"""
- Configure the `.ScalarFormatter` used by default for linear Axes.
- If a parameter is not set, the corresponding property of the formatter
- is left unchanged.
- Parameters
- ----------
- axis : {'x', 'y', 'both'}, default: 'both'
- The axis to configure. Only major ticks are affected.
- style : {'sci', 'scientific', 'plain'}
- Whether to use scientific notation.
- The formatter default is to use scientific notation.
- scilimits : pair of ints (m, n)
- Scientific notation is used only for numbers outside the range
- 10\ :sup:`m` to 10\ :sup:`n` (and only if the formatter is
- configured to use scientific notation at all). Use (0, 0) to
- include all numbers. Use (m, m) where m != 0 to fix the order of
- magnitude to 10\ :sup:`m`.
- The formatter default is :rc:`axes.formatter.limits`.
- useOffset : bool or float
- If True, the offset is calculated as needed.
- If False, no offset is used.
- If a numeric value, it sets the offset.
- The formatter default is :rc:`axes.formatter.useoffset`.
- useLocale : bool
- Whether to format the number using the current locale or using the
- C (English) locale. This affects e.g. the decimal separator. The
- formatter default is :rc:`axes.formatter.use_locale`.
- useMathText : bool
- Render the offset and scientific notation in mathtext.
- The formatter default is :rc:`axes.formatter.use_mathtext`.
- Raises
- ------
- AttributeError
- If the current formatter is not a `.ScalarFormatter`.
- """
- style = style.lower()
- axis = axis.lower()
- if scilimits is not None:
- try:
- m, n = scilimits
- m + n + 1 # check that both are numbers
- except (ValueError, TypeError) as err:
- raise ValueError("scilimits must be a sequence of 2 integers"
- ) from err
- STYLES = {'sci': True, 'scientific': True, 'plain': False, '': None}
- is_sci_style = _api.check_getitem(STYLES, style=style)
- axis_map = {**{k: [v] for k, v in self._axis_map.items()},
- 'both': list(self._axis_map.values())}
- axises = _api.check_getitem(axis_map, axis=axis)
- try:
- for axis in axises:
- if is_sci_style is not None:
- axis.major.formatter.set_scientific(is_sci_style)
- if scilimits is not None:
- axis.major.formatter.set_powerlimits(scilimits)
- if useOffset is not None:
- axis.major.formatter.set_useOffset(useOffset)
- if useLocale is not None:
- axis.major.formatter.set_useLocale(useLocale)
- if useMathText is not None:
- axis.major.formatter.set_useMathText(useMathText)
- except AttributeError as err:
- raise AttributeError(
- "This method only works with the ScalarFormatter") from err
- def locator_params(self, axis='both', tight=None, **kwargs):
- """
- Control behavior of major tick locators.
- Because the locator is involved in autoscaling, `~.Axes.autoscale_view`
- is called automatically after the parameters are changed.
- Parameters
- ----------
- axis : {'both', 'x', 'y'}, default: 'both'
- The axis on which to operate. (For 3D Axes, *axis* can also be
- set to 'z', and 'both' refers to all three axes.)
- tight : bool or None, optional
- Parameter passed to `~.Axes.autoscale_view`.
- Default is None, for no change.
- Other Parameters
- ----------------
- **kwargs
- Remaining keyword arguments are passed to directly to the
- ``set_params()`` method of the locator. Supported keywords depend
- on the type of the locator. See for example
- `~.ticker.MaxNLocator.set_params` for the `.ticker.MaxNLocator`
- used by default for linear.
- Examples
- --------
- When plotting small subplots, one might want to reduce the maximum
- number of ticks and use tight bounds, for example::
- ax.locator_params(tight=True, nbins=4)
- """
- _api.check_in_list([*self._axis_names, "both"], axis=axis)
- for name in self._axis_names:
- if axis in [name, "both"]:
- loc = self._axis_map[name].get_major_locator()
- loc.set_params(**kwargs)
- self._request_autoscale_view(name, tight=tight)
- self.stale = True
- def tick_params(self, axis='both', **kwargs):
- """
- Change the appearance of ticks, tick labels, and gridlines.
- Tick properties that are not explicitly set using the keyword
- arguments remain unchanged unless *reset* is True. For the current
- style settings, see `.Axis.get_tick_params`.
- Parameters
- ----------
- axis : {'x', 'y', 'both'}, default: 'both'
- The axis to which the parameters are applied.
- which : {'major', 'minor', 'both'}, default: 'major'
- The group of ticks to which the parameters are applied.
- reset : bool, default: False
- Whether to reset the ticks to defaults before updating them.
- Other Parameters
- ----------------
- direction : {'in', 'out', 'inout'}
- Puts ticks inside the Axes, outside the Axes, or both.
- length : float
- Tick length in points.
- width : float
- Tick width in points.
- color : color
- Tick color.
- pad : float
- Distance in points between tick and label.
- labelsize : float or str
- Tick label font size in points or as a string (e.g., 'large').
- labelcolor : color
- Tick label color.
- labelfontfamily : str
- Tick label font.
- colors : color
- Tick color and label color.
- zorder : float
- Tick and label zorder.
- bottom, top, left, right : bool
- Whether to draw the respective ticks.
- labelbottom, labeltop, labelleft, labelright : bool
- Whether to draw the respective tick labels.
- labelrotation : float
- Tick label rotation
- grid_color : color
- Gridline color.
- grid_alpha : float
- Transparency of gridlines: 0 (transparent) to 1 (opaque).
- grid_linewidth : float
- Width of gridlines in points.
- grid_linestyle : str
- Any valid `.Line2D` line style spec.
- Examples
- --------
- ::
- ax.tick_params(direction='out', length=6, width=2, colors='r',
- grid_color='r', grid_alpha=0.5)
- This will make all major ticks be red, pointing out of the box,
- and with dimensions 6 points by 2 points. Tick labels will
- also be red. Gridlines will be red and translucent.
- """
- _api.check_in_list(['x', 'y', 'both'], axis=axis)
- if axis in ['x', 'both']:
- xkw = dict(kwargs)
- xkw.pop('left', None)
- xkw.pop('right', None)
- xkw.pop('labelleft', None)
- xkw.pop('labelright', None)
- self.xaxis.set_tick_params(**xkw)
- if axis in ['y', 'both']:
- ykw = dict(kwargs)
- ykw.pop('top', None)
- ykw.pop('bottom', None)
- ykw.pop('labeltop', None)
- ykw.pop('labelbottom', None)
- self.yaxis.set_tick_params(**ykw)
- def set_axis_off(self):
- """
- Hide all visual components of the x- and y-axis.
- This sets a flag to suppress drawing of all axis decorations, i.e.
- axis labels, axis spines, and the axis tick component (tick markers,
- tick labels, and grid lines). Individual visibility settings of these
- components are ignored as long as `set_axis_off()` is in effect.
- """
- self.axison = False
- self.stale = True
- def set_axis_on(self):
- """
- Do not hide all visual components of the x- and y-axis.
- This reverts the effect of a prior `.set_axis_off()` call. Whether the
- individual axis decorations are drawn is controlled by their respective
- visibility settings.
- This is on by default.
- """
- self.axison = True
- self.stale = True
- # data limits, ticks, tick labels, and formatting
- def get_xlabel(self):
- """
- Get the xlabel text string.
- """
- label = self.xaxis.get_label()
- return label.get_text()
- def set_xlabel(self, xlabel, fontdict=None, labelpad=None, *,
- loc=None, **kwargs):
- """
- Set the label for the x-axis.
- Parameters
- ----------
- xlabel : str
- The label text.
- labelpad : float, default: :rc:`axes.labelpad`
- Spacing in points from the Axes bounding box including ticks
- and tick labels. If None, the previous value is left as is.
- loc : {'left', 'center', 'right'}, default: :rc:`xaxis.labellocation`
- The label position. This is a high-level alternative for passing
- parameters *x* and *horizontalalignment*.
- Other Parameters
- ----------------
- **kwargs : `~matplotlib.text.Text` properties
- `.Text` properties control the appearance of the label.
- See Also
- --------
- text : Documents the properties supported by `.Text`.
- """
- if labelpad is not None:
- self.xaxis.labelpad = labelpad
- protected_kw = ['x', 'horizontalalignment', 'ha']
- if {*kwargs} & {*protected_kw}:
- if loc is not None:
- raise TypeError(f"Specifying 'loc' is disallowed when any of "
- f"its corresponding low level keyword "
- f"arguments ({protected_kw}) are also "
- f"supplied")
- else:
- loc = (loc if loc is not None
- else mpl.rcParams['xaxis.labellocation'])
- _api.check_in_list(('left', 'center', 'right'), loc=loc)
- x = {
- 'left': 0,
- 'center': 0.5,
- 'right': 1,
- }[loc]
- kwargs.update(x=x, horizontalalignment=loc)
- return self.xaxis.set_label_text(xlabel, fontdict, **kwargs)
- def invert_xaxis(self):
- """
- Invert the x-axis.
- See Also
- --------
- xaxis_inverted
- get_xlim, set_xlim
- get_xbound, set_xbound
- """
- self.xaxis.set_inverted(not self.xaxis.get_inverted())
- xaxis_inverted = _axis_method_wrapper("xaxis", "get_inverted")
- def get_xbound(self):
- """
- Return the lower and upper x-axis bounds, in increasing order.
- See Also
- --------
- set_xbound
- get_xlim, set_xlim
- invert_xaxis, xaxis_inverted
- """
- left, right = self.get_xlim()
- if left < right:
- return left, right
- else:
- return right, left
- def set_xbound(self, lower=None, upper=None):
- """
- Set the lower and upper numerical bounds of the x-axis.
- This method will honor axis inversion regardless of parameter order.
- It will not change the autoscaling setting (`.get_autoscalex_on()`).
- Parameters
- ----------
- lower, upper : float or None
- The lower and upper bounds. If *None*, the respective axis bound
- is not modified.
- .. ACCEPTS: (lower: float, upper: float)
- See Also
- --------
- get_xbound
- get_xlim, set_xlim
- invert_xaxis, xaxis_inverted
- """
- if upper is None and np.iterable(lower):
- lower, upper = lower
- old_lower, old_upper = self.get_xbound()
- if lower is None:
- lower = old_lower
- if upper is None:
- upper = old_upper
- self.set_xlim(sorted((lower, upper),
- reverse=bool(self.xaxis_inverted())),
- auto=None)
- def get_xlim(self):
- """
- Return the x-axis view limits.
- Returns
- -------
- left, right : (float, float)
- The current x-axis limits in data coordinates.
- See Also
- --------
- .Axes.set_xlim
- .Axes.set_xbound, .Axes.get_xbound
- .Axes.invert_xaxis, .Axes.xaxis_inverted
- Notes
- -----
- The x-axis may be inverted, in which case the *left* value will
- be greater than the *right* value.
- """
- return tuple(self.viewLim.intervalx)
- def _validate_converted_limits(self, limit, convert):
- """
- Raise ValueError if converted limits are non-finite.
- Note that this function also accepts None as a limit argument.
- Returns
- -------
- The limit value after call to convert(), or None if limit is None.
- """
- if limit is not None:
- converted_limit = convert(limit)
- if isinstance(converted_limit, np.ndarray):
- converted_limit = converted_limit.squeeze()
- if (isinstance(converted_limit, Real)
- and not np.isfinite(converted_limit)):
- raise ValueError("Axis limits cannot be NaN or Inf")
- return converted_limit
- def set_xlim(self, left=None, right=None, *, emit=True, auto=False,
- xmin=None, xmax=None):
- """
- Set the x-axis view limits.
- Parameters
- ----------
- left : float, optional
- The left xlim in data coordinates. Passing *None* leaves the
- limit unchanged.
- The left and right xlims may also be passed as the tuple
- (*left*, *right*) as the first positional argument (or as
- the *left* keyword argument).
- .. ACCEPTS: (left: float, right: float)
- right : float, optional
- The right xlim in data coordinates. Passing *None* leaves the
- limit unchanged.
- emit : bool, default: True
- Whether to notify observers of limit change.
- auto : bool or None, default: False
- Whether to turn on autoscaling of the x-axis. True turns on,
- False turns off, None leaves unchanged.
- xmin, xmax : float, optional
- They are equivalent to left and right respectively, and it is an
- error to pass both *xmin* and *left* or *xmax* and *right*.
- Returns
- -------
- left, right : (float, float)
- The new x-axis limits in data coordinates.
- See Also
- --------
- get_xlim
- set_xbound, get_xbound
- invert_xaxis, xaxis_inverted
- Notes
- -----
- The *left* value may be greater than the *right* value, in which
- case the x-axis values will decrease from left to right.
- Examples
- --------
- >>> set_xlim(left, right)
- >>> set_xlim((left, right))
- >>> left, right = set_xlim(left, right)
- One limit may be left unchanged.
- >>> set_xlim(right=right_lim)
- Limits may be passed in reverse order to flip the direction of
- the x-axis. For example, suppose *x* represents the number of
- years before present. The x-axis limits might be set like the
- following so 5000 years ago is on the left of the plot and the
- present is on the right.
- >>> set_xlim(5000, 0)
- """
- if right is None and np.iterable(left):
- left, right = left
- if xmin is not None:
- if left is not None:
- raise TypeError("Cannot pass both 'left' and 'xmin'")
- left = xmin
- if xmax is not None:
- if right is not None:
- raise TypeError("Cannot pass both 'right' and 'xmax'")
- right = xmax
- return self.xaxis._set_lim(left, right, emit=emit, auto=auto)
- get_xscale = _axis_method_wrapper("xaxis", "get_scale")
- set_xscale = _axis_method_wrapper("xaxis", "_set_axes_scale")
- get_xticks = _axis_method_wrapper("xaxis", "get_ticklocs")
- set_xticks = _axis_method_wrapper("xaxis", "set_ticks",
- doc_sub={'set_ticks': 'set_xticks'})
- get_xmajorticklabels = _axis_method_wrapper("xaxis", "get_majorticklabels")
- get_xminorticklabels = _axis_method_wrapper("xaxis", "get_minorticklabels")
- get_xticklabels = _axis_method_wrapper("xaxis", "get_ticklabels")
- set_xticklabels = _axis_method_wrapper(
- "xaxis", "set_ticklabels",
- doc_sub={"Axis.set_ticks": "Axes.set_xticks"})
- def get_ylabel(self):
- """
- Get the ylabel text string.
- """
- label = self.yaxis.get_label()
- return label.get_text()
- def set_ylabel(self, ylabel, fontdict=None, labelpad=None, *,
- loc=None, **kwargs):
- """
- Set the label for the y-axis.
- Parameters
- ----------
- ylabel : str
- The label text.
- labelpad : float, default: :rc:`axes.labelpad`
- Spacing in points from the Axes bounding box including ticks
- and tick labels. If None, the previous value is left as is.
- loc : {'bottom', 'center', 'top'}, default: :rc:`yaxis.labellocation`
- The label position. This is a high-level alternative for passing
- parameters *y* and *horizontalalignment*.
- Other Parameters
- ----------------
- **kwargs : `~matplotlib.text.Text` properties
- `.Text` properties control the appearance of the label.
- See Also
- --------
- text : Documents the properties supported by `.Text`.
- """
- if labelpad is not None:
- self.yaxis.labelpad = labelpad
- protected_kw = ['y', 'horizontalalignment', 'ha']
- if {*kwargs} & {*protected_kw}:
- if loc is not None:
- raise TypeError(f"Specifying 'loc' is disallowed when any of "
- f"its corresponding low level keyword "
- f"arguments ({protected_kw}) are also "
- f"supplied")
- else:
- loc = (loc if loc is not None
- else mpl.rcParams['yaxis.labellocation'])
- _api.check_in_list(('bottom', 'center', 'top'), loc=loc)
- y, ha = {
- 'bottom': (0, 'left'),
- 'center': (0.5, 'center'),
- 'top': (1, 'right')
- }[loc]
- kwargs.update(y=y, horizontalalignment=ha)
- return self.yaxis.set_label_text(ylabel, fontdict, **kwargs)
- def invert_yaxis(self):
- """
- Invert the y-axis.
- See Also
- --------
- yaxis_inverted
- get_ylim, set_ylim
- get_ybound, set_ybound
- """
- self.yaxis.set_inverted(not self.yaxis.get_inverted())
- yaxis_inverted = _axis_method_wrapper("yaxis", "get_inverted")
- def get_ybound(self):
- """
- Return the lower and upper y-axis bounds, in increasing order.
- See Also
- --------
- set_ybound
- get_ylim, set_ylim
- invert_yaxis, yaxis_inverted
- """
- bottom, top = self.get_ylim()
- if bottom < top:
- return bottom, top
- else:
- return top, bottom
- def set_ybound(self, lower=None, upper=None):
- """
- Set the lower and upper numerical bounds of the y-axis.
- This method will honor axis inversion regardless of parameter order.
- It will not change the autoscaling setting (`.get_autoscaley_on()`).
- Parameters
- ----------
- lower, upper : float or None
- The lower and upper bounds. If *None*, the respective axis bound
- is not modified.
- .. ACCEPTS: (lower: float, upper: float)
- See Also
- --------
- get_ybound
- get_ylim, set_ylim
- invert_yaxis, yaxis_inverted
- """
- if upper is None and np.iterable(lower):
- lower, upper = lower
- old_lower, old_upper = self.get_ybound()
- if lower is None:
- lower = old_lower
- if upper is None:
- upper = old_upper
- self.set_ylim(sorted((lower, upper),
- reverse=bool(self.yaxis_inverted())),
- auto=None)
- def get_ylim(self):
- """
- Return the y-axis view limits.
- Returns
- -------
- bottom, top : (float, float)
- The current y-axis limits in data coordinates.
- See Also
- --------
- .Axes.set_ylim
- .Axes.set_ybound, .Axes.get_ybound
- .Axes.invert_yaxis, .Axes.yaxis_inverted
- Notes
- -----
- The y-axis may be inverted, in which case the *bottom* value
- will be greater than the *top* value.
- """
- return tuple(self.viewLim.intervaly)
- def set_ylim(self, bottom=None, top=None, *, emit=True, auto=False,
- ymin=None, ymax=None):
- """
- Set the y-axis view limits.
- Parameters
- ----------
- bottom : float, optional
- The bottom ylim in data coordinates. Passing *None* leaves the
- limit unchanged.
- The bottom and top ylims may also be passed as the tuple
- (*bottom*, *top*) as the first positional argument (or as
- the *bottom* keyword argument).
- .. ACCEPTS: (bottom: float, top: float)
- top : float, optional
- The top ylim in data coordinates. Passing *None* leaves the
- limit unchanged.
- emit : bool, default: True
- Whether to notify observers of limit change.
- auto : bool or None, default: False
- Whether to turn on autoscaling of the y-axis. *True* turns on,
- *False* turns off, *None* leaves unchanged.
- ymin, ymax : float, optional
- They are equivalent to bottom and top respectively, and it is an
- error to pass both *ymin* and *bottom* or *ymax* and *top*.
- Returns
- -------
- bottom, top : (float, float)
- The new y-axis limits in data coordinates.
- See Also
- --------
- get_ylim
- set_ybound, get_ybound
- invert_yaxis, yaxis_inverted
- Notes
- -----
- The *bottom* value may be greater than the *top* value, in which
- case the y-axis values will decrease from *bottom* to *top*.
- Examples
- --------
- >>> set_ylim(bottom, top)
- >>> set_ylim((bottom, top))
- >>> bottom, top = set_ylim(bottom, top)
- One limit may be left unchanged.
- >>> set_ylim(top=top_lim)
- Limits may be passed in reverse order to flip the direction of
- the y-axis. For example, suppose ``y`` represents depth of the
- ocean in m. The y-axis limits might be set like the following
- so 5000 m depth is at the bottom of the plot and the surface,
- 0 m, is at the top.
- >>> set_ylim(5000, 0)
- """
- if top is None and np.iterable(bottom):
- bottom, top = bottom
- if ymin is not None:
- if bottom is not None:
- raise TypeError("Cannot pass both 'bottom' and 'ymin'")
- bottom = ymin
- if ymax is not None:
- if top is not None:
- raise TypeError("Cannot pass both 'top' and 'ymax'")
- top = ymax
- return self.yaxis._set_lim(bottom, top, emit=emit, auto=auto)
- get_yscale = _axis_method_wrapper("yaxis", "get_scale")
- set_yscale = _axis_method_wrapper("yaxis", "_set_axes_scale")
- get_yticks = _axis_method_wrapper("yaxis", "get_ticklocs")
- set_yticks = _axis_method_wrapper("yaxis", "set_ticks",
- doc_sub={'set_ticks': 'set_yticks'})
- get_ymajorticklabels = _axis_method_wrapper("yaxis", "get_majorticklabels")
- get_yminorticklabels = _axis_method_wrapper("yaxis", "get_minorticklabels")
- get_yticklabels = _axis_method_wrapper("yaxis", "get_ticklabels")
- set_yticklabels = _axis_method_wrapper(
- "yaxis", "set_ticklabels",
- doc_sub={"Axis.set_ticks": "Axes.set_yticks"})
- xaxis_date = _axis_method_wrapper("xaxis", "axis_date")
- yaxis_date = _axis_method_wrapper("yaxis", "axis_date")
- def format_xdata(self, x):
- """
- Return *x* formatted as an x-value.
- This function will use the `.fmt_xdata` attribute if it is not None,
- else will fall back on the xaxis major formatter.
- """
- return (self.fmt_xdata if self.fmt_xdata is not None
- else self.xaxis.get_major_formatter().format_data_short)(x)
- def format_ydata(self, y):
- """
- Return *y* formatted as a y-value.
- This function will use the `.fmt_ydata` attribute if it is not None,
- else will fall back on the yaxis major formatter.
- """
- return (self.fmt_ydata if self.fmt_ydata is not None
- else self.yaxis.get_major_formatter().format_data_short)(y)
- def format_coord(self, x, y):
- """Return a format string formatting the *x*, *y* coordinates."""
- return "x={} y={}".format(
- "???" if x is None else self.format_xdata(x),
- "???" if y is None else self.format_ydata(y),
- )
- def minorticks_on(self):
- """
- Display minor ticks on the Axes.
- Displaying minor ticks may reduce performance; you may turn them off
- using `minorticks_off()` if drawing speed is a problem.
- """
- for ax in (self.xaxis, self.yaxis):
- scale = ax.get_scale()
- if scale == 'log':
- s = ax._scale
- ax.set_minor_locator(mticker.LogLocator(s.base, s.subs))
- elif scale == 'symlog':
- s = ax._scale
- ax.set_minor_locator(
- mticker.SymmetricalLogLocator(s._transform, s.subs))
- else:
- ax.set_minor_locator(mticker.AutoMinorLocator())
- def minorticks_off(self):
- """Remove minor ticks from the Axes."""
- self.xaxis.set_minor_locator(mticker.NullLocator())
- self.yaxis.set_minor_locator(mticker.NullLocator())
- # Interactive manipulation
- def can_zoom(self):
- """
- Return whether this Axes supports the zoom box button functionality.
- """
- return True
- def can_pan(self):
- """
- Return whether this Axes supports any pan/zoom button functionality.
- """
- return True
- def get_navigate(self):
- """
- Get whether the Axes responds to navigation commands.
- """
- return self._navigate
- def set_navigate(self, b):
- """
- Set whether the Axes responds to navigation toolbar commands.
- Parameters
- ----------
- b : bool
- """
- self._navigate = b
- def get_navigate_mode(self):
- """
- Get the navigation toolbar button status: 'PAN', 'ZOOM', or None.
- """
- return self._navigate_mode
- def set_navigate_mode(self, b):
- """
- Set the navigation toolbar button status.
- .. warning::
- This is not a user-API function.
- """
- self._navigate_mode = b
- def _get_view(self):
- """
- Save information required to reproduce the current view.
- This method is called before a view is changed, such as during a pan or zoom
- initiated by the user. It returns an opaque object that describes the current
- view, in a format compatible with :meth:`_set_view`.
- The default implementation saves the view limits and autoscaling state.
- Subclasses may override this as needed, as long as :meth:`_set_view` is also
- adjusted accordingly.
- """
- return {
- "xlim": self.get_xlim(), "autoscalex_on": self.get_autoscalex_on(),
- "ylim": self.get_ylim(), "autoscaley_on": self.get_autoscaley_on(),
- }
- def _set_view(self, view):
- """
- Apply a previously saved view.
- This method is called when restoring a view (with the return value of
- :meth:`_get_view` as argument), such as with the navigation buttons.
- Subclasses that override :meth:`_get_view` also need to override this method
- accordingly.
- """
- self.set(**view)
- def _prepare_view_from_bbox(self, bbox, direction='in',
- mode=None, twinx=False, twiny=False):
- """
- Helper function to prepare the new bounds from a bbox.
- This helper function returns the new x and y bounds from the zoom
- bbox. This a convenience method to abstract the bbox logic
- out of the base setter.
- """
- if len(bbox) == 3:
- xp, yp, scl = bbox # Zooming code
- if scl == 0: # Should not happen
- scl = 1.
- if scl > 1:
- direction = 'in'
- else:
- direction = 'out'
- scl = 1/scl
- # get the limits of the axes
- (xmin, ymin), (xmax, ymax) = self.transData.transform(
- np.transpose([self.get_xlim(), self.get_ylim()]))
- # set the range
- xwidth = xmax - xmin
- ywidth = ymax - ymin
- xcen = (xmax + xmin)*.5
- ycen = (ymax + ymin)*.5
- xzc = (xp*(scl - 1) + xcen)/scl
- yzc = (yp*(scl - 1) + ycen)/scl
- bbox = [xzc - xwidth/2./scl, yzc - ywidth/2./scl,
- xzc + xwidth/2./scl, yzc + ywidth/2./scl]
- elif len(bbox) != 4:
- # should be len 3 or 4 but nothing else
- _api.warn_external(
- "Warning in _set_view_from_bbox: bounding box is not a tuple "
- "of length 3 or 4. Ignoring the view change.")
- return
- # Original limits.
- xmin0, xmax0 = self.get_xbound()
- ymin0, ymax0 = self.get_ybound()
- # The zoom box in screen coords.
- startx, starty, stopx, stopy = bbox
- # Convert to data coords.
- (startx, starty), (stopx, stopy) = self.transData.inverted().transform(
- [(startx, starty), (stopx, stopy)])
- # Clip to axes limits.
- xmin, xmax = np.clip(sorted([startx, stopx]), xmin0, xmax0)
- ymin, ymax = np.clip(sorted([starty, stopy]), ymin0, ymax0)
- # Don't double-zoom twinned axes or if zooming only the other axis.
- if twinx or mode == "y":
- xmin, xmax = xmin0, xmax0
- if twiny or mode == "x":
- ymin, ymax = ymin0, ymax0
- if direction == "in":
- new_xbound = xmin, xmax
- new_ybound = ymin, ymax
- elif direction == "out":
- x_trf = self.xaxis.get_transform()
- sxmin0, sxmax0, sxmin, sxmax = x_trf.transform(
- [xmin0, xmax0, xmin, xmax]) # To screen space.
- factor = (sxmax0 - sxmin0) / (sxmax - sxmin) # Unzoom factor.
- # Move original bounds away by
- # (factor) x (distance between unzoom box and Axes bbox).
- sxmin1 = sxmin0 - factor * (sxmin - sxmin0)
- sxmax1 = sxmax0 + factor * (sxmax0 - sxmax)
- # And back to data space.
- new_xbound = x_trf.inverted().transform([sxmin1, sxmax1])
- y_trf = self.yaxis.get_transform()
- symin0, symax0, symin, symax = y_trf.transform(
- [ymin0, ymax0, ymin, ymax])
- factor = (symax0 - symin0) / (symax - symin)
- symin1 = symin0 - factor * (symin - symin0)
- symax1 = symax0 + factor * (symax0 - symax)
- new_ybound = y_trf.inverted().transform([symin1, symax1])
- return new_xbound, new_ybound
- def _set_view_from_bbox(self, bbox, direction='in',
- mode=None, twinx=False, twiny=False):
- """
- Update view from a selection bbox.
- .. note::
- Intended to be overridden by new projection types, but if not, the
- default implementation sets the view limits to the bbox directly.
- Parameters
- ----------
- bbox : 4-tuple or 3 tuple
- * If bbox is a 4 tuple, it is the selected bounding box limits,
- in *display* coordinates.
- * If bbox is a 3 tuple, it is an (xp, yp, scl) triple, where
- (xp, yp) is the center of zooming and scl the scale factor to
- zoom by.
- direction : str
- The direction to apply the bounding box.
- * `'in'` - The bounding box describes the view directly, i.e.,
- it zooms in.
- * `'out'` - The bounding box describes the size to make the
- existing view, i.e., it zooms out.
- mode : str or None
- The selection mode, whether to apply the bounding box in only the
- `'x'` direction, `'y'` direction or both (`None`).
- twinx : bool
- Whether this axis is twinned in the *x*-direction.
- twiny : bool
- Whether this axis is twinned in the *y*-direction.
- """
- new_xbound, new_ybound = self._prepare_view_from_bbox(
- bbox, direction=direction, mode=mode, twinx=twinx, twiny=twiny)
- if not twinx and mode != "y":
- self.set_xbound(new_xbound)
- self.set_autoscalex_on(False)
- if not twiny and mode != "x":
- self.set_ybound(new_ybound)
- self.set_autoscaley_on(False)
- def start_pan(self, x, y, button):
- """
- Called when a pan operation has started.
- Parameters
- ----------
- x, y : float
- The mouse coordinates in display coords.
- button : `.MouseButton`
- The pressed mouse button.
- Notes
- -----
- This is intended to be overridden by new projection types.
- """
- self._pan_start = types.SimpleNamespace(
- lim=self.viewLim.frozen(),
- trans=self.transData.frozen(),
- trans_inverse=self.transData.inverted().frozen(),
- bbox=self.bbox.frozen(),
- x=x,
- y=y)
- def end_pan(self):
- """
- Called when a pan operation completes (when the mouse button is up.)
- Notes
- -----
- This is intended to be overridden by new projection types.
- """
- del self._pan_start
- def _get_pan_points(self, button, key, x, y):
- """
- Helper function to return the new points after a pan.
- This helper function returns the points on the axis after a pan has
- occurred. This is a convenience method to abstract the pan logic
- out of the base setter.
- """
- def format_deltas(key, dx, dy):
- if key == 'control':
- if abs(dx) > abs(dy):
- dy = dx
- else:
- dx = dy
- elif key == 'x':
- dy = 0
- elif key == 'y':
- dx = 0
- elif key == 'shift':
- if 2 * abs(dx) < abs(dy):
- dx = 0
- elif 2 * abs(dy) < abs(dx):
- dy = 0
- elif abs(dx) > abs(dy):
- dy = dy / abs(dy) * abs(dx)
- else:
- dx = dx / abs(dx) * abs(dy)
- return dx, dy
- p = self._pan_start
- dx = x - p.x
- dy = y - p.y
- if dx == dy == 0:
- return
- if button == 1:
- dx, dy = format_deltas(key, dx, dy)
- result = p.bbox.translated(-dx, -dy).transformed(p.trans_inverse)
- elif button == 3:
- try:
- dx = -dx / self.bbox.width
- dy = -dy / self.bbox.height
- dx, dy = format_deltas(key, dx, dy)
- if self.get_aspect() != 'auto':
- dx = dy = 0.5 * (dx + dy)
- alpha = np.power(10.0, (dx, dy))
- start = np.array([p.x, p.y])
- oldpoints = p.lim.transformed(p.trans)
- newpoints = start + alpha * (oldpoints - start)
- result = (mtransforms.Bbox(newpoints)
- .transformed(p.trans_inverse))
- except OverflowError:
- _api.warn_external('Overflow while panning')
- return
- else:
- return
- valid = np.isfinite(result.transformed(p.trans))
- points = result.get_points().astype(object)
- # Just ignore invalid limits (typically, underflow in log-scale).
- points[~valid] = None
- return points
- def drag_pan(self, button, key, x, y):
- """
- Called when the mouse moves during a pan operation.
- Parameters
- ----------
- button : `.MouseButton`
- The pressed mouse button.
- key : str or None
- The pressed key, if any.
- x, y : float
- The mouse coordinates in display coords.
- Notes
- -----
- This is intended to be overridden by new projection types.
- """
- points = self._get_pan_points(button, key, x, y)
- if points is not None:
- self.set_xlim(points[:, 0])
- self.set_ylim(points[:, 1])
- def get_children(self):
- # docstring inherited.
- return [
- *self._children,
- *self.spines.values(),
- *self._axis_map.values(),
- self.title, self._left_title, self._right_title,
- *self.child_axes,
- *([self.legend_] if self.legend_ is not None else []),
- self.patch,
- ]
- def contains(self, mouseevent):
- # docstring inherited.
- return self.patch.contains(mouseevent)
- def contains_point(self, point):
- """
- Return whether *point* (pair of pixel coordinates) is inside the Axes
- patch.
- """
- return self.patch.contains_point(point, radius=1.0)
- def get_default_bbox_extra_artists(self):
- """
- Return a default list of artists that are used for the bounding box
- calculation.
- Artists are excluded either by not being visible or
- ``artist.set_in_layout(False)``.
- """
- artists = self.get_children()
- for axis in self._axis_map.values():
- # axis tight bboxes are calculated separately inside
- # Axes.get_tightbbox() using for_layout_only=True
- artists.remove(axis)
- if not (self.axison and self._frameon):
- # don't do bbox on spines if frame not on.
- for spine in self.spines.values():
- artists.remove(spine)
- artists.remove(self.title)
- artists.remove(self._left_title)
- artists.remove(self._right_title)
- # always include types that do not internally implement clipping
- # to Axes. may have clip_on set to True and clip_box equivalent
- # to ax.bbox but then ignore these properties during draws.
- noclip = (_AxesBase, maxis.Axis,
- offsetbox.AnnotationBbox, offsetbox.OffsetBox)
- return [a for a in artists if a.get_visible() and a.get_in_layout()
- and (isinstance(a, noclip) or not a._fully_clipped_to_axes())]
- @_api.make_keyword_only("3.8", "call_axes_locator")
- def get_tightbbox(self, renderer=None, call_axes_locator=True,
- bbox_extra_artists=None, *, for_layout_only=False):
- """
- Return the tight bounding box of the Axes, including axis and their
- decorators (xlabel, title, etc).
- Artists that have ``artist.set_in_layout(False)`` are not included
- in the bbox.
- Parameters
- ----------
- renderer : `.RendererBase` subclass
- renderer that will be used to draw the figures (i.e.
- ``fig.canvas.get_renderer()``)
- bbox_extra_artists : list of `.Artist` or ``None``
- List of artists to include in the tight bounding box. If
- ``None`` (default), then all artist children of the Axes are
- included in the tight bounding box.
- call_axes_locator : bool, default: True
- If *call_axes_locator* is ``False``, it does not call the
- ``_axes_locator`` attribute, which is necessary to get the correct
- bounding box. ``call_axes_locator=False`` can be used if the
- caller is only interested in the relative size of the tightbbox
- compared to the Axes bbox.
- for_layout_only : default: False
- The bounding box will *not* include the x-extent of the title and
- the xlabel, or the y-extent of the ylabel.
- Returns
- -------
- `.BboxBase`
- Bounding box in figure pixel coordinates.
- See Also
- --------
- matplotlib.axes.Axes.get_window_extent
- matplotlib.axis.Axis.get_tightbbox
- matplotlib.spines.Spine.get_window_extent
- """
- bb = []
- if renderer is None:
- renderer = self.figure._get_renderer()
- if not self.get_visible():
- return None
- locator = self.get_axes_locator()
- self.apply_aspect(
- locator(self, renderer) if locator and call_axes_locator else None)
- for axis in self._axis_map.values():
- if self.axison and axis.get_visible():
- ba = martist._get_tightbbox_for_layout_only(axis, renderer)
- if ba:
- bb.append(ba)
- self._update_title_position(renderer)
- axbbox = self.get_window_extent(renderer)
- bb.append(axbbox)
- for title in [self.title, self._left_title, self._right_title]:
- if title.get_visible():
- bt = title.get_window_extent(renderer)
- if for_layout_only and bt.width > 0:
- # make the title bbox 1 pixel wide so its width
- # is not accounted for in bbox calculations in
- # tight/constrained_layout
- bt.x0 = (bt.x0 + bt.x1) / 2 - 0.5
- bt.x1 = bt.x0 + 1.0
- bb.append(bt)
- bbox_artists = bbox_extra_artists
- if bbox_artists is None:
- bbox_artists = self.get_default_bbox_extra_artists()
- for a in bbox_artists:
- bbox = a.get_tightbbox(renderer)
- if (bbox is not None
- and 0 < bbox.width < np.inf
- and 0 < bbox.height < np.inf):
- bb.append(bbox)
- return mtransforms.Bbox.union(
- [b for b in bb if b.width != 0 or b.height != 0])
- def _make_twin_axes(self, *args, **kwargs):
- """Make a twinx Axes of self. This is used for twinx and twiny."""
- if 'sharex' in kwargs and 'sharey' in kwargs:
- # The following line is added in v2.2 to avoid breaking Seaborn,
- # which currently uses this internal API.
- if kwargs["sharex"] is not self and kwargs["sharey"] is not self:
- raise ValueError("Twinned Axes may share only one axis")
- ss = self.get_subplotspec()
- if ss:
- twin = self.figure.add_subplot(ss, *args, **kwargs)
- else:
- twin = self.figure.add_axes(
- self.get_position(True), *args, **kwargs,
- axes_locator=_TransformedBoundsLocator(
- [0, 0, 1, 1], self.transAxes))
- self.set_adjustable('datalim')
- twin.set_adjustable('datalim')
- self._twinned_axes.join(self, twin)
- return twin
- def twinx(self):
- """
- Create a twin Axes sharing the xaxis.
- Create a new Axes with an invisible x-axis and an independent
- y-axis positioned opposite to the original one (i.e. at right). The
- x-axis autoscale setting will be inherited from the original
- Axes. To ensure that the tick marks of both y-axes align, see
- `~matplotlib.ticker.LinearLocator`.
- Returns
- -------
- Axes
- The newly created Axes instance
- Notes
- -----
- For those who are 'picking' artists while using twinx, pick
- events are only called for the artists in the top-most Axes.
- """
- ax2 = self._make_twin_axes(sharex=self)
- ax2.yaxis.tick_right()
- ax2.yaxis.set_label_position('right')
- ax2.yaxis.set_offset_position('right')
- ax2.set_autoscalex_on(self.get_autoscalex_on())
- self.yaxis.tick_left()
- ax2.xaxis.set_visible(False)
- ax2.patch.set_visible(False)
- ax2.xaxis.units = self.xaxis.units
- return ax2
- def twiny(self):
- """
- Create a twin Axes sharing the yaxis.
- Create a new Axes with an invisible y-axis and an independent
- x-axis positioned opposite to the original one (i.e. at top). The
- y-axis autoscale setting will be inherited from the original Axes.
- To ensure that the tick marks of both x-axes align, see
- `~matplotlib.ticker.LinearLocator`.
- Returns
- -------
- Axes
- The newly created Axes instance
- Notes
- -----
- For those who are 'picking' artists while using twiny, pick
- events are only called for the artists in the top-most Axes.
- """
- ax2 = self._make_twin_axes(sharey=self)
- ax2.xaxis.tick_top()
- ax2.xaxis.set_label_position('top')
- ax2.set_autoscaley_on(self.get_autoscaley_on())
- self.xaxis.tick_bottom()
- ax2.yaxis.set_visible(False)
- ax2.patch.set_visible(False)
- ax2.yaxis.units = self.yaxis.units
- return ax2
- def get_shared_x_axes(self):
- """Return an immutable view on the shared x-axes Grouper."""
- return cbook.GrouperView(self._shared_axes["x"])
- def get_shared_y_axes(self):
- """Return an immutable view on the shared y-axes Grouper."""
- return cbook.GrouperView(self._shared_axes["y"])
- def label_outer(self, remove_inner_ticks=False):
- """
- Only show "outer" labels and tick labels.
- x-labels are only kept for subplots on the last row (or first row, if
- labels are on the top side); y-labels only for subplots on the first
- column (or last column, if labels are on the right side).
- Parameters
- ----------
- remove_inner_ticks : bool, default: False
- If True, remove the inner ticks as well (not only tick labels).
- .. versionadded:: 3.8
- """
- self._label_outer_xaxis(skip_non_rectangular_axes=False,
- remove_inner_ticks=remove_inner_ticks)
- self._label_outer_yaxis(skip_non_rectangular_axes=False,
- remove_inner_ticks=remove_inner_ticks)
- def _label_outer_xaxis(self, *, skip_non_rectangular_axes,
- remove_inner_ticks=False):
- # see documentation in label_outer.
- if skip_non_rectangular_axes and not isinstance(self.patch,
- mpl.patches.Rectangle):
- return
- ss = self.get_subplotspec()
- if not ss:
- return
- label_position = self.xaxis.get_label_position()
- if not ss.is_first_row(): # Remove top label/ticklabels/offsettext.
- if label_position == "top":
- self.set_xlabel("")
- top_kw = {'top': False} if remove_inner_ticks else {}
- self.xaxis.set_tick_params(
- which="both", labeltop=False, **top_kw)
- if self.xaxis.offsetText.get_position()[1] == 1:
- self.xaxis.offsetText.set_visible(False)
- if not ss.is_last_row(): # Remove bottom label/ticklabels/offsettext.
- if label_position == "bottom":
- self.set_xlabel("")
- bottom_kw = {'bottom': False} if remove_inner_ticks else {}
- self.xaxis.set_tick_params(
- which="both", labelbottom=False, **bottom_kw)
- if self.xaxis.offsetText.get_position()[1] == 0:
- self.xaxis.offsetText.set_visible(False)
- def _label_outer_yaxis(self, *, skip_non_rectangular_axes,
- remove_inner_ticks=False):
- # see documentation in label_outer.
- if skip_non_rectangular_axes and not isinstance(self.patch,
- mpl.patches.Rectangle):
- return
- ss = self.get_subplotspec()
- if not ss:
- return
- label_position = self.yaxis.get_label_position()
- if not ss.is_first_col(): # Remove left label/ticklabels/offsettext.
- if label_position == "left":
- self.set_ylabel("")
- left_kw = {'left': False} if remove_inner_ticks else {}
- self.yaxis.set_tick_params(
- which="both", labelleft=False, **left_kw)
- if self.yaxis.offsetText.get_position()[0] == 0:
- self.yaxis.offsetText.set_visible(False)
- if not ss.is_last_col(): # Remove right label/ticklabels/offsettext.
- if label_position == "right":
- self.set_ylabel("")
- right_kw = {'right': False} if remove_inner_ticks else {}
- self.yaxis.set_tick_params(
- which="both", labelright=False, **right_kw)
- if self.yaxis.offsetText.get_position()[0] == 1:
- self.yaxis.offsetText.set_visible(False)
- def _draw_rasterized(figure, artists, renderer):
- """
- A helper function for rasterizing the list of artists.
- The bookkeeping to track if we are or are not in rasterizing mode
- with the mixed-mode backends is relatively complicated and is now
- handled in the matplotlib.artist.allow_rasterization decorator.
- This helper defines the absolute minimum methods and attributes on a
- shim class to be compatible with that decorator and then uses it to
- rasterize the list of artists.
- This is maybe too-clever, but allows us to re-use the same code that is
- used on normal artists to participate in the "are we rasterizing"
- accounting.
- Please do not use this outside of the "rasterize below a given zorder"
- functionality of Axes.
- Parameters
- ----------
- figure : matplotlib.figure.Figure
- The figure all of the artists belong to (not checked). We need this
- because we can at the figure level suppress composition and insert each
- rasterized artist as its own image.
- artists : List[matplotlib.artist.Artist]
- The list of Artists to be rasterized. These are assumed to all
- be in the same Figure.
- renderer : matplotlib.backendbases.RendererBase
- The currently active renderer
- Returns
- -------
- None
- """
- class _MinimalArtist:
- def get_rasterized(self):
- return True
- def get_agg_filter(self):
- return None
- def __init__(self, figure, artists):
- self.figure = figure
- self.artists = artists
- @martist.allow_rasterization
- def draw(self, renderer):
- for a in self.artists:
- a.draw(renderer)
- return _MinimalArtist(figure, artists).draw(renderer)
|