1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769377037713772377337743775377637773778377937803781378237833784378537863787378837893790379137923793379437953796379737983799380038013802380338043805380638073808380938103811381238133814381538163817381838193820382138223823382438253826382738283829383038313832383338343835383638373838383938403841384238433844384538463847384838493850385138523853385438553856385738583859386038613862386338643865386638673868386938703871387238733874387538763877387838793880388138823883388438853886388738883889389038913892389338943895389638973898389939003901390239033904390539063907390839093910391139123913391439153916391739183919392039213922392339243925392639273928392939303931393239333934393539363937393839393940394139423943394439453946394739483949395039513952395339543955395639573958395939603961396239633964396539663967396839693970397139723973397439753976397739783979398039813982398339843985398639873988398939903991399239933994399539963997399839994000400140024003400440054006400740084009401040114012401340144015401640174018401940204021402240234024402540264027402840294030403140324033403440354036403740384039404040414042404340444045404640474048404940504051405240534054405540564057405840594060406140624063406440654066406740684069407040714072407340744075407640774078407940804081408240834084408540864087408840894090409140924093409440954096409740984099410041014102410341044105410641074108410941104111411241134114411541164117411841194120412141224123412441254126412741284129413041314132413341344135413641374138413941404141414241434144414541464147414841494150415141524153415441554156415741584159416041614162416341644165416641674168416941704171417241734174417541764177417841794180418141824183418441854186418741884189419041914192419341944195419641974198419942004201420242034204420542064207420842094210421142124213421442154216421742184219422042214222422342244225422642274228422942304231423242334234423542364237423842394240424142424243424442454246424742484249425042514252425342544255425642574258425942604261426242634264426542664267426842694270427142724273427442754276427742784279428042814282428342844285428642874288428942904291429242934294429542964297429842994300430143024303430443054306430743084309431043114312431343144315431643174318431943204321432243234324432543264327432843294330433143324333433443354336433743384339434043414342434343444345434643474348434943504351435243534354435543564357435843594360436143624363436443654366436743684369437043714372437343744375437643774378437943804381438243834384438543864387438843894390439143924393439443954396439743984399440044014402440344044405440644074408440944104411441244134414441544164417441844194420442144224423442444254426442744284429443044314432443344344435443644374438443944404441 |
- from collections import OrderedDict
- import itertools
- import logging
- import math
- from numbers import Real
- from operator import attrgetter
- import types
- import numpy as np
- import matplotlib as mpl
- from matplotlib import cbook, rcParams
- from matplotlib.cbook import _OrderedSet, _check_1d, index_of
- from matplotlib import docstring
- import matplotlib.colors as mcolors
- import matplotlib.lines as mlines
- import matplotlib.patches as mpatches
- import matplotlib.artist as martist
- import matplotlib.transforms as mtransforms
- import matplotlib.ticker as mticker
- import matplotlib.axis as maxis
- import matplotlib.spines as mspines
- import matplotlib.font_manager as font_manager
- import matplotlib.text as mtext
- import matplotlib.image as mimage
- from matplotlib.rcsetup import cycler, validate_axisbelow
- _log = logging.getLogger(__name__)
- def _process_plot_format(fmt):
- """
- 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
- 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.
- 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(
- 'Illegal format string "%s"; two linestyle symbols' % fmt)
- linestyle = fmt[i:i+2]
- i += 2
- elif c in mlines.lineStyles:
- if linestyle is not None:
- raise ValueError(
- 'Illegal format string "%s"; two linestyle symbols' % fmt)
- linestyle = c
- i += 1
- elif c in mlines.lineMarkers:
- if marker is not None:
- raise ValueError(
- 'Illegal format string "%s"; two marker symbols' % fmt)
- marker = c
- i += 1
- elif c in mcolors.get_named_colors_mapping():
- if color is not None:
- raise ValueError(
- 'Illegal format string "%s"; two color symbols' % fmt)
- color = c
- i += 1
- elif c == 'C' and i < len(fmt) - 1:
- color_cycle_number = int(fmt[i + 1])
- color = mcolors.to_rgba("C{}".format(color_cycle_number))
- i += 2
- else:
- raise ValueError(
- 'Unrecognized character %c in format string' % c)
- if linestyle is None and marker is None:
- linestyle = 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 the plot command, so that
- plot commands like the following are supported::
- 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, axes, command='plot'):
- self.axes = axes
- self.command = command
- self.set_prop_cycle()
- def __getstate__(self):
- # note: it is not possible to pickle a generator (and thus a cycler).
- return {'axes': self.axes, 'command': self.command}
- def __setstate__(self, state):
- self.__dict__ = state.copy()
- self.set_prop_cycle()
- def set_prop_cycle(self, *args, **kwargs):
- # Can't do `args == (None,)` as that crashes cycler.
- if not (args or kwargs) or (len(args) == 1 and args[0] is None):
- prop_cycler = rcParams['axes.prop_cycle']
- else:
- prop_cycler = cycler(*args, **kwargs)
- self.prop_cycler = itertools.cycle(prop_cycler)
- # This should make a copy
- self._prop_keys = prop_cycler.keys
- def __call__(self, *args, **kwargs):
- self.axes._process_unit_info(kwargs=kwargs)
- for pos_only in "xy":
- if pos_only in kwargs:
- raise TypeError("{} got an unexpected keyword argument {!r}"
- .format(self.command, pos_only))
- if not args:
- return
- # Process the 'data' kwarg.
- data = kwargs.pop("data", None)
- if data is not None:
- 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)
- cbook._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
- # 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(this, kwargs)
- def get_next_color(self):
- """Return the next color in the cycle."""
- if 'color' not in self._prop_keys:
- return 'k'
- return next(self.prop_cycler)['color']
- 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 = next(self.prop_cycler).copy()
- 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, 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
- def _makefill(self, x, y, kw, kwargs):
- # Polygon doesn't directly support unitized inputs.
- x = self.axes.convert_xunits(x)
- y = self.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
- def _plot_args(self, tup, kwargs):
- if len(tup) > 1 and isinstance(tup[-1], str):
- linestyle, marker, color = _process_plot_format(tup[-1])
- tup = tup[:-1]
- elif len(tup) == 3:
- raise ValueError('third arg must be a format string')
- else:
- 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 k, v in zip(('linestyle', 'marker', 'color'),
- (linestyle, marker, color)):
- if v is not None:
- kw[k] = v
- if len(tup) == 2:
- x = _check_1d(tup[0])
- y = _check_1d(tup[-1])
- else:
- x, y = index_of(tup[-1])
- if self.axes.xaxis is not None:
- self.axes.xaxis.update_units(x)
- if self.axes.yaxis is not None:
- self.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 2-D, 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':
- func = self._makeline
- else:
- kw['closed'] = kwargs.get('closed', True)
- func = self._makefill
- ncx, ncy = x.shape[1], y.shape[1]
- if ncx > 1 and ncy > 1 and ncx != ncy:
- cbook.warn_deprecated(
- "2.2", message="cycling among columns of inputs with "
- "non-matching shapes is deprecated.")
- return [func(x[:, j % ncx], y[:, j % ncy], kw, kwargs)
- for j in range(max(ncx, ncy))]
- class _AxesBase(martist.Artist):
- name = "rectilinear"
- _shared_x_axes = cbook.Grouper()
- _shared_y_axes = cbook.Grouper()
- _twinned_axes = cbook.Grouper()
- 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, rect,
- 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,
- **kwargs
- ):
- """
- Build an axes in a figure.
- Parameters
- ----------
- fig : `~matplotlib.figure.Figure`
- The axes is build in the `.Figure` *fig*.
- rect : [left, bottom, width, height]
- The axes is build in the rectangle *rect*. *rect* is in
- `.Figure` coordinates.
- sharex, sharey : `~.axes.Axes`, optional
- The x or y `~.matplotlib.axis` is shared with the x or
- y axis in the input `~.axes.Axes`.
- frameon : bool, optional
- True means that the axes frame is visible.
- **kwargs
- Other optional keyword arguments:
- %(Axes)s
- Returns
- -------
- axes : `~.axes.Axes`
- The new `~.axes.Axes` object.
- """
- martist.Artist.__init__(self)
- if isinstance(rect, mtransforms.Bbox):
- self._position = rect
- else:
- self._position = mtransforms.Bbox.from_bounds(*rect)
- 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_viewlim_x = False
- self._stale_viewlim_y = False
- self._sharex = sharex
- self._sharey = sharey
- if sharex is not None:
- self._shared_x_axes.join(self, sharex)
- if sharey is not None:
- self._shared_y_axes.join(self, sharey)
- self.set_label(label)
- self.set_figure(fig)
- self.set_axes_locator(kwargs.get("axes_locator", None))
- self.spines = self._gen_axes_spines()
- # this call may differ for non-sep axes, e.g., polar
- self._init_axis()
- if facecolor is None:
- facecolor = rcParams['axes.facecolor']
- self._facecolor = facecolor
- self._frameon = frameon
- self.set_axisbelow(rcParams['axes.axisbelow'])
- self._rasterization_zorder = None
- self.cla()
- # 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.update(kwargs)
- if self.xaxis is not None:
- self._xcid = self.xaxis.callbacks.connect(
- 'units finalize', lambda: self._on_units_changed(scalex=True))
- if self.yaxis is not None:
- self._ycid = self.yaxis.callbacks.connect(
- 'units finalize', lambda: self._on_units_changed(scaley=True))
- 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')
- self._layoutbox = None
- self._poslayoutbox = None
- def __getstate__(self):
- # The renderer should be re-created by the figure, and then cached at
- # that point.
- state = super().__getstate__()
- for key in ['_layoutbox', '_poslayoutbox']:
- state[key] = None
- # Prune the sharing & twinning info to only contain the current group.
- for grouper_name in [
- '_shared_x_axes', '_shared_y_axes', '_twinned_axes']:
- grouper = getattr(self, grouper_name)
- state[grouper_name] = (grouper.get_siblings(self)
- if self in grouper else None)
- return state
- def __setstate__(self, state):
- # Merge the grouping info back into the global groupers.
- for grouper_name in [
- '_shared_x_axes', '_shared_y_axes', '_twinned_axes']:
- siblings = state.pop(grouper_name)
- if siblings:
- getattr(self, grouper_name).join(*siblings)
- self.__dict__ = state
- self._stale = True
- def get_window_extent(self, *args, **kwargs):
- """
- Return the axes bounding box in display space; *args* and *kwargs*
- are empty.
- This bounding box does not include the spines, ticks, ticklables,
- 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.get_window_extent
- """
- return self.bbox
- def _init_axis(self):
- "move this out of __init__ because non-separable axes don't use it"
- self.xaxis = maxis.XAxis(self)
- self.spines['bottom'].register_axis(self.xaxis)
- self.spines['top'].register_axis(self.xaxis)
- self.yaxis = maxis.YAxis(self)
- self.spines['left'].register_axis(self.yaxis)
- self.spines['right'].register_axis(self.yaxis)
- self._update_transScale()
- def set_figure(self, fig):
- """
- Set the `.Figure` for this `.Axes`.
- Parameters
- ----------
- fig : `.Figure`
- """
- martist.Artist.set_figure(self, fig)
- self.bbox = mtransforms.TransformedBbox(self._position,
- fig.transFigure)
- # 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.
- scalex = any(ax._stale_viewlim_x
- for ax in self._shared_x_axes.get_siblings(self))
- scaley = any(ax._stale_viewlim_y
- for ax in self._shared_y_axes.get_siblings(self))
- if scalex or scaley:
- for ax in self._shared_x_axes.get_siblings(self):
- ax._stale_viewlim_x = False
- for ax in self._shared_y_axes.get_siblings(self):
- ax._stale_viewlim_y = False
- self.autoscale_view(scalex=scalex, scaley=scaley)
- @property
- def viewLim(self):
- self._unstale_viewLim()
- return self._viewLim
- # API could be better, right now this is just to match the old calls to
- # autoscale_view() after each plotting method.
- def _request_autoscale_view(self, tight=None, scalex=True, scaley=True):
- if tight is not None:
- self._tight = tight
- if scalex:
- self._stale_viewlim_x = True # Else keep old state.
- if scaley:
- self._stale_viewlim_y = True
- 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.
- """
- 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('unknown value for which')
- 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 axes and the label.
- The x-direction is in data coordinates and the y-direction is in
- axis corrdinates
- 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 = 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 axes and the
- label. The x-direction is in data coordinates and the y-direction
- is in axis corrdinates
- 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 = 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.
- """
- 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('unknown value for which')
- 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 axes and the label.
- The x-direction is in axis coordinates and the y-direction is in
- data corrdinates
- 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 = 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 axes and the
- label. The x-direction is in axis coordinates and the y-direction
- is in data corrdinates
- 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 = 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()))
- for line in getattr(self, "lines", []): # Not set during init.
- try:
- line._transformed_path.invalidate()
- except AttributeError:
- pass
- def get_position(self, original=False):
- """
- Get a copy of the axes rectangle 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
- -------
- pos : `.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 `.set_aspect`
- for details.
- Parameters
- ----------
- pos : [left, bottom, width, height] or `~matplotlib.transforms.Bbox`
- The new position of the in `.Figure` coordinates.
- which : {'both', 'active', 'original'}, optional
- Determines which position variables to change.
- """
- self._set_position(pos, which=which)
- # because this is being called externally to the library we
- # zero the constrained layout parts.
- self._layoutbox = None
- self._poslayoutbox = None
- def _set_position(self, pos, which='both'):
- """
- private version of set_position. Call this internally
- to get the same functionality of `get_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 resets the a possible position change due to aspect constraints.
- For an explanation of the positions see `.set_position`.
- """
- 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.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 OrderedDict((side, mspines.Spine.linear_spine(self, side))
- for side in ['left', 'right', 'bottom', 'top'])
- def cla(self):
- """Clear the current axes."""
- # Note: this is called by Axes.__init__()
- # 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()
- self.xaxis.cla()
- self.yaxis.cla()
- for name, spine in self.spines.items():
- spine.cla()
- self.ignore_existing_data_limits = True
- self.callbacks = cbook.CallbackRegistry()
- if self._sharex is not None:
- # major and minor are axis.Ticker class instances with
- # locator and formatter attributes
- self.xaxis.major = self._sharex.xaxis.major
- self.xaxis.minor = self._sharex.xaxis.minor
- x0, x1 = self._sharex.get_xlim()
- self.set_xlim(x0, x1, emit=False,
- auto=self._sharex.get_autoscalex_on())
- self.xaxis._scale = self._sharex.xaxis._scale
- else:
- self.xaxis._set_scale('linear')
- try:
- self.set_xlim(0, 1)
- except TypeError:
- pass
- if self._sharey is not None:
- self.yaxis.major = self._sharey.yaxis.major
- self.yaxis.minor = self._sharey.yaxis.minor
- y0, y1 = self._sharey.get_ylim()
- self.set_ylim(y0, y1, emit=False,
- auto=self._sharey.get_autoscaley_on())
- self.yaxis._scale = self._sharey.yaxis._scale
- else:
- self.yaxis._set_scale('linear')
- try:
- self.set_ylim(0, 1)
- except TypeError:
- pass
- # update the minor locator for x and y axis based on rcParams
- if rcParams['xtick.minor.visible']:
- self.xaxis.set_minor_locator(mticker.AutoMinorLocator())
- if rcParams['ytick.minor.visible']:
- self.yaxis.set_minor_locator(mticker.AutoMinorLocator())
- if self._sharex is None:
- self._autoscaleXon = True
- if self._sharey is None:
- self._autoscaleYon = True
- self._xmargin = rcParams['axes.xmargin']
- self._ymargin = rcParams['axes.ymargin']
- self._tight = None
- self._use_sticky_edges = True
- self._update_transScale() # needed?
- self._get_lines = _process_plot_var_args(self)
- self._get_patches_for_fill = _process_plot_var_args(self, 'fill')
- self._gridOn = rcParams['axes.grid']
- self.lines = []
- self.patches = []
- self.texts = []
- self.tables = []
- self.artists = []
- self.images = []
- self._mouseover_set = _OrderedSet()
- self.child_axes = []
- self._current_image = None # strictly for pyplot via _sci, _gci
- self.legend_ = None
- self.collections = [] # collection.Collection instances
- self.containers = []
- self.grid(False) # Disable grid on init to use rcParameter
- self.grid(self._gridOn, which=rcParams['axes.grid.which'],
- axis=rcParams['axes.grid.axis'])
- props = font_manager.FontProperties(
- size=rcParams['axes.titlesize'],
- weight=rcParams['axes.titleweight'])
- self.title = mtext.Text(
- x=0.5, y=1.0, text='',
- fontproperties=props,
- verticalalignment='baseline',
- horizontalalignment='center',
- )
- self._left_title = mtext.Text(
- x=0.0, y=1.0, text='',
- fontproperties=props.copy(),
- verticalalignment='baseline',
- horizontalalignment='left', )
- self._right_title = mtext.Text(
- x=1.0, y=1.0, text='',
- fontproperties=props.copy(),
- verticalalignment='baseline',
- horizontalalignment='right',
- )
- title_offset_points = 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)
- # determine if the title position has been set manually:
- self._autotitlepos = None
- 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)
- self._shared_x_axes.clean()
- self._shared_y_axes.clean()
- if self._sharex:
- self.xaxis.set_visible(xaxis_visible)
- self.patch.set_visible(patch_visible)
- if self._sharey:
- self.yaxis.set_visible(yaxis_visible)
- self.patch.set_visible(patch_visible)
- self.stale = True
- def clear(self):
- """Clear the axes."""
- self.cla()
- def get_facecolor(self):
- """Get the facecolor of the Axes."""
- return self.patch.get_facecolor()
- get_fc = 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)
- set_fc = set_facecolor
- def _set_title_offset_trans(self, title_offset_points):
- """
- Set the offset for the title either from rcParams['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
- Set the given Cycler. *None* resets to the cycle defined by the
- current style.
- 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.
- 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'])
- See Also
- --------
- matplotlib.rcsetup.cycler
- Convenience function for creating validated cyclers for properties.
- cycler.cycler
- The original function for creating unvalidated cyclers.
- """
- 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 self._aspect
- def set_aspect(self, aspect, adjustable=None, anchor=None, share=False):
- """
- Set the aspect of the axis scaling, i.e. the ratio of y-unit to x-unit.
- Parameters
- ----------
- aspect : {'auto', 'equal'} or num
- Possible values:
- ======== ================================================
- value description
- ======== ================================================
- 'auto' automatic; fill the position rectangle with data
- 'equal' same scaling from data to plot units for x and y
- num a circle will be stretched such that the height
- is num times the width. aspect=1 is the same as
- aspect='equal'.
- ======== ================================================
- 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 2-tuple of 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
- 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 `.set_anchor` for further details.
- share : bool, optional
- If ``True``, apply the settings to all shared Axes.
- Default is ``False``.
- See Also
- --------
- matplotlib.axes.Axes.set_adjustable
- defining the parameter to adjust in order to meet the required
- aspect.
- matplotlib.axes.Axes.set_anchor
- defining the position in case of extra space.
- """
- if not (cbook._str_equal(aspect, 'equal')
- or cbook._str_equal(aspect, 'auto')):
- aspect = float(aspect) # raise ValueError if necessary
- if (not cbook._str_equal(aspect, 'auto')) and self.name == '3d':
- raise NotImplementedError(
- 'It is not currently possible to manually set the aspect '
- 'on 3D axes')
- if share:
- axes = {*self._shared_x_axes.get_siblings(self),
- *self._shared_y_axes.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 self._adjustable
- def set_adjustable(self, adjustable, share=False):
- """
- Define which parameter the Axes will change to achieve a given aspect.
- Parameters
- ----------
- adjustable : {'box', 'datalim'}
- If 'box', change the physical dimensions of the Axes.
- If 'datalim', change the ``x`` or ``y`` data limits.
- share : bool, optional
- If ``True``, apply the settings to all shared Axes.
- Default is ``False``.
- 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.
- """
- cbook._check_in_list(["box", "datalim"], adjustable=adjustable)
- if share:
- axs = {*self._shared_x_axes.get_siblings(self),
- *self._shared_y_axes.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_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 : 2-tuple of floats or {'C', 'SW', 'S', 'SE', ...}
- The anchor position may be either:
- - a sequence (*cx*, *cy*). *cx* and *cy* may range from 0
- to 1, where 0 is left or bottom and 1 is right or top.
- - a string using cardinal directions as abbreviation:
- - 'C' for centered
- - 'S' (south) for bottom-center
- - 'SW' (south west) for bottom-left
- - etc.
- Here is an overview of the possible positions:
- +------+------+------+
- | 'NW' | 'N' | 'NE' |
- +------+------+------+
- | 'W' | 'C' | 'E' |
- +------+------+------+
- | 'SW' | 'S' | 'SE' |
- +------+------+------+
- share : bool, optional
- If ``True``, apply the settings to all shared Axes.
- Default is ``False``.
- 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 = {*self._shared_x_axes.get_siblings(self),
- *self._shared_y_axes.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
- @cbook.deprecated("3.2")
- def get_data_ratio_log(self):
- """
- Return the aspect ratio of the raw data in log scale.
- Notes
- -----
- Will be used when both axis are in log scale.
- """
- xmin, xmax = self.get_xbound()
- ymin, ymax = self.get_ybound()
- xsize = max(abs(math.log10(xmax) - math.log10(xmin)), 1e-30)
- ysize = max(abs(math.log10(ymax) - math.log10(ymin)), 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, `.get_anchor`
- will affect the position.
- 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
- defining the parameter to adjust in order to meet the required
- aspect.
- matplotlib.axes.Axes.set_anchor
- defining the position in case of extra space.
- """
- if position is None:
- position = self.get_position(original=True)
- aspect = self.get_aspect()
- if aspect == 'auto':
- self._set_position(position, which='active')
- return
- if aspect == 'equal':
- aspect = 1
- fig_width, fig_height = self.get_figure().get_size_inches()
- fig_aspect = fig_height / fig_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
- # self._adjustable == 'datalim'
- # reset active to original in case it had been changed by prior use
- # of 'box'
- self._set_position(position, which='active')
- 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)
- l, b, w, h = position.bounds
- box_aspect = fig_aspect * (h / w)
- 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_x_axes
- shared_y = self in self._shared_y_axes
- # Not sure whether we need this check:
- if shared_x and shared_y:
- raise RuntimeError("adjustable='datalim' is not allowed when both "
- "axes are shared")
- # 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, *args, 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
- ======== ==========================================================
- 'on' Turn on axis lines and labels. Same as ``True``.
- 'off' Turn off axis lines and labels. Same as ``False``.
- 'equal' Set equal scaling (i.e., make circles circular) by
- changing 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).
- 'normal' Same as 'auto'; deprecated.
- 'image' 'scaled' with axis limits equal to data limits.
- 'square' Square plot; similar to 'scaled', but initially forcing
- ``xmax-xmin == ymax-ymin``.
- ======== ==========================================================
- emit : bool, optional, 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
- """
- if len(args) == 1 and isinstance(args[0], (str, bool)):
- s = args[0]
- if s is True:
- s = 'on'
- if s is False:
- s = 'off'
- s = s.lower()
- if s == 'on':
- self.set_axis_on()
- elif s == 'off':
- self.set_axis_off()
- elif s in ('equal', 'tight', 'scaled', 'normal',
- 'auto', 'image', 'square'):
- if s == 'normal':
- cbook.warn_deprecated(
- "3.1", message="Passing 'normal' to axis() is "
- "deprecated since %(since)s; use 'auto' instead.")
- self.set_autoscale_on(True)
- self.set_aspect('auto')
- self.autoscale_view(tight=False)
- # self.apply_aspect()
- if s == 'equal':
- self.set_aspect('equal', adjustable='datalim')
- elif s == 'scaled':
- self.set_aspect('equal', adjustable='box', anchor='C')
- self.set_autoscale_on(False) # Req. by Mark Bakker
- elif s == 'tight':
- self.autoscale_view(tight=True)
- self.set_autoscale_on(False)
- elif s == 'image':
- self.autoscale_view(tight=True)
- self.set_autoscale_on(False)
- self.set_aspect('equal', adjustable='box', anchor='C')
- elif s == '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('Unrecognized string %s to axis; '
- 'try on or off' % s)
- else:
- if len(args) >= 1:
- if len(args) != 1:
- cbook.warn_deprecated(
- "3.2", message="Passing more than one positional "
- "argument to axis() is deprecated and will raise a "
- "TypeError %(removal)s.")
- limits = args[0]
- try:
- xmin, xmax, ymin, ymax = limits
- except (TypeError, ValueError):
- raise TypeError('the first argument to axis() must be an '
- 'interable of the form '
- '[xmin, xmax, ymin, ymax]')
- else:
- xmin = kwargs.pop('xmin', None)
- xmax = kwargs.pop('xmax', None)
- ymin = kwargs.pop('ymin', None)
- ymax = kwargs.pop('ymax', None)
- xauto = (None # Keep autoscale state as is.
- if xmin is None and xmax is None
- else False) # Turn off autoscale.
- yauto = (None
- if ymin is None and ymax is None
- else False)
- self.set_xlim(xmin, xmax, emit=emit, auto=xauto)
- self.set_ylim(ymin, ymax, emit=emit, auto=yauto)
- if kwargs:
- cbook.warn_deprecated(
- "3.1", message="Passing unsupported keyword arguments to "
- "axis() will raise a TypeError %(removal)s.")
- return (*self.get_xlim(), *self.get_ylim())
- def get_legend(self):
- """Return the `Legend` instance, or None if no legend is defined."""
- return self.legend_
- def get_images(self):
- """return a list of Axes images 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):
- """Return the XAxis instance."""
- return self.xaxis
- def get_xgridlines(self):
- """Get the x grid lines as a list of `Line2D` instances."""
- return self.xaxis.get_gridlines()
- def get_xticklines(self):
- """Get the x tick lines as a list of `Line2D` instances."""
- return self.xaxis.get_ticklines()
- def get_yaxis(self):
- """Return the YAxis instance."""
- return self.yaxis
- def get_ygridlines(self):
- """Get the y grid lines as a list of `Line2D` instances."""
- return self.yaxis.get_gridlines()
- def get_yticklines(self):
- """Get the y tick lines as a list of `Line2D` instances."""
- return self.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.
- """
- if isinstance(im, mpl.contour.ContourSet):
- if im.collections[0] not in self.collections:
- raise ValueError("ContourSet must be in current Axes")
- elif im not in self.images and im not in self.collections:
- raise ValueError("Argument must be an image, collection, or "
- "ContourSet in this Axes")
- self._current_image = im
- def _gci(self):
- """
- Helper for :func:`~matplotlib.pyplot.gci`;
- do not use elsewhere.
- """
- return self._current_image
- def has_data(self):
- """
- Return *True* if any artists have been added to axes.
- This should not be used to determine whether the *dataLim*
- need to be updated, and may not actually be useful for
- anything.
- """
- return (
- len(self.collections) +
- len(self.images) +
- len(self.lines) +
- len(self.patches)) > 0
- def add_artist(self, a):
- """
- Add an `~.Artist` to the axes, and 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.artists.append(a)
- a._remove_method = self.artists.remove
- self._set_artist_props(a)
- 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 = self.child_axes.remove
- self.stale = True
- return ax
- def add_collection(self, collection, autolim=True):
- """
- Add a `~.Collection` to the axes' collections; return the collection.
- """
- label = collection.get_label()
- if not label:
- collection.set_label('_collection%d' % len(self.collections))
- self.collections.append(collection)
- collection._remove_method = self.collections.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()
- self.update_datalim(collection.get_datalim(self.transData))
- self.stale = True
- return collection
- def add_image(self, image):
- """
- Add an `~.AxesImage` to the axes' images; return the image.
- """
- self._set_artist_props(image)
- if not image.get_label():
- image.set_label('_image%d' % len(self.images))
- self.images.append(image)
- image._remove_method = self.images.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' lines; return the 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('_line%d' % len(self.lines))
- self.lines.append(line)
- line._remove_method = self.lines.remove
- self.stale = True
- return line
- def _add_text(self, txt):
- """
- Add a `~.Text` to the axes' texts; return the text.
- """
- self._set_artist_props(txt)
- self.texts.append(txt)
- txt._remove_method = self.texts.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_trans = line.get_transform()
- if line_trans == self.transData:
- data_path = path
- elif any(line_trans.contains_branch_seperately(self.transData)):
- # identify the transform to go from line's coordinates
- # to data coordinates
- trans_to_data = line_trans - 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_trans is
- # entirely encapsulated in trans_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 = trans_to_data.transform_path_affine(na_path)
- else:
- data_path = trans_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 data_path.vertices.size > 0:
- updatex, updatey = line_trans.contains_branch_seperately(
- self.transData)
- 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' patches; return the patch.
- """
- self._set_artist_props(p)
- if p.get_clip_path() is None:
- p.set_clip_path(self.patch)
- self._update_patch_limits(p)
- self.patches.append(p)
- p._remove_method = self.patches.remove
- return p
- def _update_patch_limits(self, patch):
- """update the data limits for patch *p*"""
- # 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
- vertices = patch.get_path().vertices
- if vertices.size > 0:
- xys = patch.get_patch_transform().transform(vertices)
- if patch.get_data_transform() != self.transData:
- patch_to_data = (patch.get_data_transform() -
- self.transData)
- xys = patch_to_data.transform(xys)
- updatex, updatey = patch.get_transform().\
- contains_branch_seperately(self.transData)
- self.update_datalim(xys, updatex=updatex,
- updatey=updatey)
- def add_table(self, tab):
- """
- Add a `~.Table` to the axes' tables; return the table.
- """
- self._set_artist_props(tab)
- self.tables.append(tab)
- tab.set_clip_path(self.patch)
- tab._remove_method = self.tables.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 _on_units_changed(self, scalex=False, scaley=False):
- """
- Callback for processing changes to axis units.
- Currently requests updates of data limits and view limits.
- """
- self.relim()
- self._request_autoscale_view(scalex=scalex, scaley=scaley)
- 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
- Whether to exclude invisible artists. Defaults to False.
- """
- # 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 line in self.lines:
- if not visible_only or line.get_visible():
- self._update_line_limits(line)
- for p in self.patches:
- if not visible_only or p.get_visible():
- self._update_patch_limits(p)
- for image in self.images:
- if not visible_only or image.get_visible():
- self._update_image_limits(image)
- 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 Nx2 array.
- updatex, updatey : bool, optional, default *True*
- Whether to update the x/y limits.
- """
- xys = np.asarray(xys)
- if not len(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 update_datalim_bounds(self, bounds):
- """
- Extend the `~.Axes.datalim` Bbox to include the given
- `~matplotlib.transforms.Bbox`.
- Parameters
- ----------
- bounds : `~matplotlib.transforms.Bbox`
- """
- self.dataLim.set(mtransforms.Bbox.union([self.dataLim, bounds]))
- def _process_unit_info(self, xdata=None, ydata=None, kwargs=None):
- """Look for unit *kwargs* and update the axis instances as necessary"""
- def _process_single_axis(data, axis, unit_name, kwargs):
- # Return if there's no axis set
- if axis is None:
- return kwargs
- if data is not None:
- # We only need to update if there is nothing set yet.
- if not axis.have_units():
- axis.update_units(data)
- # Check for units in the kwargs, and if present update axis
- if kwargs is not None:
- units = kwargs.pop(unit_name, axis.units)
- if self.name == 'polar':
- polar_units = {'xunits': 'thetaunits', 'yunits': 'runits'}
- units = kwargs.pop(polar_units[unit_name], units)
- if units != axis.units:
- axis.set_units(units)
- # If the units being set imply a different converter,
- # we need to update.
- if data is not None:
- axis.update_units(data)
- return kwargs
- kwargs = _process_single_axis(xdata, self.xaxis, 'xunits', kwargs)
- kwargs = _process_single_axis(ydata, self.yaxis, 'yunits', kwargs)
- return kwargs
- def in_axes(self, mouseevent):
- """
- Return *True* if the given *mouseevent* (in display coords)
- is in the Axes
- """
- return self.patch.contains(mouseevent)[0]
- def get_autoscale_on(self):
- """
- Get whether autoscaling is applied for both axes on plot commands
- """
- return self._autoscaleXon and self._autoscaleYon
- def get_autoscalex_on(self):
- """
- Get whether autoscaling for the x-axis is applied on plot commands
- """
- return self._autoscaleXon
- def get_autoscaley_on(self):
- """
- Get whether autoscaling for the y-axis is applied on plot commands
- """
- return self._autoscaleYon
- def set_autoscale_on(self, b):
- """
- Set whether autoscaling is applied on plot commands
- Parameters
- ----------
- b : bool
- """
- self._autoscaleXon = b
- self._autoscaleYon = b
- def set_autoscalex_on(self, b):
- """
- Set whether autoscaling for the x-axis is applied on plot commands
- Parameters
- ----------
- b : bool
- """
- self._autoscaleXon = b
- def set_autoscaley_on(self, b):
- """
- Set whether autoscaling for the y-axis is applied on plot commands
- Parameters
- ----------
- b : bool
- """
- self._autoscaleYon = 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.
- For example, if your data is in the range [0, 2], a factor of
- ``m = 0.1`` will result in a range [-0.2, 2.2].
- Negative values -0.5 < m < 0 will result in clipping of the data range.
- I.e. for a data range [0, 2], a factor of ``m = -0.1`` will result in
- a range [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.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.
- For example, if your data is in the range [0, 2], a factor of
- ``m = 0.1`` will result in a range [-0.2, 2.2].
- Negative values -0.5 < m < 0 will result in clipping of the data range.
- I.e. for a data range [0, 2], a factor of ``m = -0.1`` will result in
- a range [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.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
- within the range [0, 1]. 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 in place 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 is True
- The *tight* parameter is passed to :meth:`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. Set *tight* to *None* will preserve
- 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 and 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:
- cbook._warn_external(f'ignoring tight={tight!r} in get mode')
- return self._xmargin, self._ymargin
- if x is not None:
- self.set_xmargin(x)
- if y is not None:
- self.set_ymargin(y)
- self._request_autoscale_view(
- tight=tight, scalex=(x is not None), scaley=(y is not None)
- )
- def set_rasterization_zorder(self, z):
- """
- Parameters
- ----------
- z : float or None
- zorder below which artists are rasterized. ``None`` means that
- artists do not get rasterized based on zorder.
- """
- 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, optional
- True (default) turns autoscaling on, False turns it off.
- None leaves the autoscaling state unchanged.
- axis : {'both', 'x', 'y'}, optional
- Which axis to operate on; default is 'both'.
- tight : bool or None, optional
- If True, first set the margins to zero. Then, this argument is
- forwarded to `autoscale_view` (regardless of its value); see the
- description of its behavior there.
- """
- if enable is None:
- scalex = True
- scaley = True
- else:
- scalex = False
- scaley = False
- if axis in ['x', 'both']:
- self._autoscaleXon = bool(enable)
- scalex = self._autoscaleXon
- if axis in ['y', 'both']:
- self._autoscaleYon = bool(enable)
- scaley = self._autoscaleYon
- if tight and scalex:
- self._xmargin = 0
- if tight and scaley:
- self._ymargin = 0
- self._request_autoscale_view(tight=tight, scalex=scalex, scaley=scaley)
- 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
- Whether to autoscale the x axis (default is True).
- scaley : bool
- Whether to autoscale the x axis (default is True).
- 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:
- # Only iterate over axes and artists if needed. The check for
- # ``hasattr(ax, "lines")`` is necessary because this can be called
- # very early in the axes init process (e.g., for twin axes) when
- # these attributes don't even exist yet, in which case
- # `get_children` would raise an AttributeError.
- if self._xmargin and scalex and self._autoscaleXon:
- x_stickies = np.sort(np.concatenate([
- artist.sticky_edges.x
- for ax in self._shared_x_axes.get_siblings(self)
- if hasattr(ax, "lines")
- for artist in ax.get_children()]))
- if self._ymargin and scaley and self._autoscaleYon:
- y_stickies = np.sort(np.concatenate([
- artist.sticky_edges.y
- for ax in self._shared_y_axes.get_siblings(self)
- if hasattr(ax, "lines")
- for artist in ax.get_children()]))
- if self.get_xscale().lower() == 'log':
- x_stickies = x_stickies[x_stickies > 0]
- if self.get_yscale().lower() == 'log':
- y_stickies = y_stickies[y_stickies > 0]
- def handle_single_axis(scale, autoscaleon, shared_axes, interval,
- minpos, axis, margin, stickies, set_bound):
- if not (scale and autoscaleon):
- return # nothing to do...
- shared = shared_axes.get_siblings(self)
- dl = [ax.dataLim for ax in shared]
- # ignore non-finite data limits if good limits exist
- finite_dl = [d for d in dl if np.isfinite(d).all()]
- if len(finite_dl):
- # if finite limits exist for atleast one axis (and the
- # other is infinite), restore the finite limits
- x_finite = [d for d in dl
- if (np.isfinite(d.intervalx).all() and
- (d not in finite_dl))]
- y_finite = [d for d in dl
- if (np.isfinite(d.intervaly).all() and
- (d not in finite_dl))]
- dl = finite_dl
- dl.extend(x_finite)
- dl.extend(y_finite)
- bb = mtransforms.BboxBase.union(dl)
- x0, x1 = getattr(bb, interval)
- # If x0 and x1 are non finite, use the locator to figure out
- # default limits.
- locator = axis.get_major_locator()
- x0, x1 = locator.nonsingular(x0, x1)
- # Prevent margin addition from crossing a sticky value. Small
- # tolerances (whose values come from isclose()) must be used due to
- # floating point issues with streamplot.
- def tol(x): return 1e-5 * abs(x) + 1e-8
- # Index of largest element < x0 + tol, if any.
- i0 = stickies.searchsorted(x0 + tol(x0)) - 1
- x0bound = stickies[i0] if i0 != -1 else None
- # Index of smallest element > x1 - tol, if any.
- i1 = stickies.searchsorted(x1 - tol(x1))
- x1bound = stickies[i1] if i1 != len(stickies) else None
- # Add the margin in figure space and then transform back, to handle
- # non-linear scales.
- minpos = getattr(bb, minpos)
- transform = axis.get_transform()
- inverse_trans = transform.inverted()
- x0, x1 = axis._scale.limit_range_for_scale(x0, x1, 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._autoscaleXon, self._shared_x_axes, 'intervalx',
- 'minposx', self.xaxis, self._xmargin, x_stickies, self.set_xbound)
- handle_single_axis(
- scaley, self._autoscaleYon, self._shared_y_axes, 'intervaly',
- 'minposy', self.yaxis, self._ymargin, y_stickies, self.set_ybound)
- def _get_axis_list(self):
- return (self.xaxis, self.yaxis)
- def _get_axis_map(self):
- """
- Return a mapping of `Axis` "names" to `Axis` instances.
- The `Axis` name is derived from the attribute under which the instance
- is stored, so e.g. for polar axes, the theta-axis is still named "x"
- and the r-axis is still named "y" (for back-compatibility).
- In practice, this means that the entries are typically "x" and "y", and
- additionally "z" for 3D axes.
- """
- d = {}
- axis_list = self._get_axis_list()
- for k, v in vars(self).items():
- if k.endswith("axis") and v in axis_list:
- d[k[:-len("axis")]] = v
- return d
- 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)
- if self._autotitlepos is None:
- for title in titles:
- x, y = title.get_position()
- if not np.isclose(y, 1.0):
- self._autotitlepos = False
- _log.debug('not adjusting title pos because a title was'
- ' already placed manually: %f', y)
- return
- self._autotitlepos = True
- for title in titles:
- x, _ = title.get_position()
- # need to start again in case of window resizing
- title.set_position((x, 1.0))
- # need to check all our twins too...
- axs = self._twinned_axes.get_siblings(self)
- # and all the children
- for ax in self.child_axes:
- if ax is not None:
- locator = ax.get_axes_locator()
- if locator:
- pos = locator(self, renderer)
- ax.apply_aspect(pos)
- else:
- ax.apply_aspect()
- axs = axs + [ax]
- top = 0
- for ax in axs:
- if (ax.xaxis.get_ticks_position() in ['top', 'unknown']
- or ax.xaxis.get_label_position() == 'top'):
- bb = ax.xaxis.get_tightbbox(renderer)
- else:
- bb = ax.get_window_extent(renderer)
- if bb is not None:
- top = max(top, bb.ymax)
- 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=None, inframe=False):
- """Draw everything (plot lines, axes, labels)"""
- if renderer is None:
- renderer = self.figure._cachedRenderer
- 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()
- if locator:
- pos = locator(self, renderer)
- self.apply_aspect(pos)
- else:
- self.apply_aspect()
- 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 or inframe:
- for _axis in self._get_axis_list():
- artists.remove(_axis)
- if inframe:
- artists.remove(self.title)
- artists.remove(self._left_title)
- artists.remove(self._right_title)
- if not self.figure.canvas.is_saving():
- artists = [a for a in artists
- if not a.get_animated() or a in self.images]
- 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):
- renderer.start_rasterizing()
- artists_rasterized = [a for a in artists
- if a.zorder < rasterization_zorder]
- artists = [a for a in artists
- if a.zorder >= rasterization_zorder]
- else:
- artists_rasterized = []
- # the patch draws the background rectangle -- the frame below
- # will draw the edges
- if self.axison and self._frameon:
- self.patch.draw(renderer)
- if artists_rasterized:
- for a in artists_rasterized:
- a.draw(renderer)
- renderer.stop_rasterizing()
- mimage._draw_list_compositing_images(renderer, self, artists)
- renderer.close_group('axes')
- self.stale = False
- def draw_artist(self, a):
- """
- This method can only be used after an initial draw which
- caches the renderer. It is used to efficiently update Axes
- data (axis ticks, labels, etc are not updated)
- """
- if self.figure._cachedRenderer is None:
- raise AttributeError("draw_artist can only be used after an "
- "initial draw which caches the renderer")
- a.draw(self.figure._cachedRenderer)
- def redraw_in_frame(self):
- """
- This method can only be used after an initial draw which
- caches the renderer. It is used to efficiently update Axes
- data (axis ticks, labels, etc are not updated)
- """
- if self.figure._cachedRenderer is None:
- raise AttributeError("redraw_in_frame can only be used after an "
- "initial draw which caches the renderer")
- self.draw(self.figure._cachedRenderer, inframe=True)
- def get_renderer_cache(self):
- return self.figure._cachedRenderer
- # 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
- -------
- axisbelow : 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
- """
- self._axisbelow = axisbelow = validate_axisbelow(b)
- if axisbelow is True:
- zorder = 0.5
- elif axisbelow is False:
- zorder = 2.5
- elif axisbelow == "line":
- zorder = 1.5
- else:
- raise ValueError("Unexpected axisbelow value")
- for axis in self._get_axis_list():
- axis.set_zorder(zorder)
- self.stale = True
- @docstring.dedent_interpd
- def grid(self, b=None, which='major', axis='both', **kwargs):
- """
- Configure the grid lines.
- Parameters
- ----------
- b : bool or None, optional
- Whether to show the grid lines. If any *kwargs* are supplied,
- it is assumed you want the grid on and *b* will be set to True.
- If *b* 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 : `.Line2D` properties
- Define the line properties of the grid, e.g.::
- grid(color='r', linestyle='-', linewidth=2)
- Valid keyword arguments are:
- %(_Line2D_docstr)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
- `~matplotlib.axis.Axis.set_zorder` method of each axis.
- """
- if len(kwargs):
- b = True
- cbook._check_in_list(['x', 'y', 'both'], axis=axis)
- if axis in ['x', 'both']:
- self.xaxis.grid(b, which=which, **kwargs)
- if axis in ['y', 'both']:
- self.yaxis.grid(b, which=which, **kwargs)
- def ticklabel_format(self, *, axis='both', style='', scilimits=None,
- useOffset=None, useLocale=None, useMathText=None):
- r"""
- Change the `~matplotlib.ticker.ScalarFormatter` used by
- default for linear axes.
- Optional keyword arguments:
- ============== =========================================
- Keyword Description
- ============== =========================================
- *axis* {'x', 'y', 'both'}
- *style* {'sci' (or 'scientific'), 'plain'}
- plain turns off scientific notation
- *scilimits* (m, n), pair of integers; if *style*
- is 'sci', scientific notation will
- be used for numbers outside the range
- 10\ :sup:`m` to 10\ :sup:`n`.
- Use (0, 0) to include all numbers.
- Use (m, m) where m != 0 to fix the order
- of magnitude to 10\ :sup:`m`.
- *useOffset* bool or float
- If True, the offset will be calculated as
- needed; if False, no offset will be used;
- if a numeric offset is specified, it will
- be used.
- *useLocale* If True, format the number according to
- the current locale. This affects things
- such as the character used for the
- decimal separator. If False, use
- C-style (English) formatting. The
- default setting is controlled by the
- axes.formatter.use_locale rcparam.
- *useMathText* If True, render the offset and scientific
- notation in mathtext
- ============== =========================================
- Only the major ticks are affected.
- If the method is called when the `~matplotlib.ticker.ScalarFormatter`
- is not the `~matplotlib.ticker.Formatter` being used, an
- `AttributeError` will be raised.
- """
- 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):
- raise ValueError("scilimits must be a sequence of 2 integers")
- STYLES = {'sci': True, 'scientific': True, 'plain': False, '': None}
- is_sci_style = cbook._check_getitem(STYLES, style=style)
- axis_map = {**{k: [v] for k, v in self._get_axis_map().items()},
- 'both': self._get_axis_list()}
- axises = cbook._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:
- raise AttributeError(
- "This method only works with the ScalarFormatter")
- 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'}, optional
- The axis on which to operate.
- 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 axes.
- 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)
- """
- _x = axis in ['x', 'both']
- _y = axis in ['y', 'both']
- if _x:
- self.xaxis.get_major_locator().set_params(**kwargs)
- if _y:
- self.yaxis.get_major_locator().set_params(**kwargs)
- self._request_autoscale_view(tight=tight, scalex=_x, scaley=_y)
- def tick_params(self, axis='both', **kwargs):
- """Change the appearance of ticks, tick labels, and gridlines.
- Parameters
- ----------
- axis : {'x', 'y', 'both'}, optional
- Which axis to apply the parameters to.
- Other Parameters
- ----------------
- axis : {'x', 'y', 'both'}
- Axis on which to operate; default is 'both'.
- reset : bool, default: False
- If *True*, set all parameters to defaults before processing other
- keyword arguments.
- which : {'major', 'minor', 'both'}
- Default is 'major'; apply arguments to *which* ticks.
- 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.
- 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
- --------
- Usage ::
- 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.
- """
- cbook._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):
- """
- Turn the x- and y-axis off.
- This affects the axis lines, ticks, ticklabels, grid and axis labels.
- """
- self.axison = False
- self.stale = True
- def set_axis_on(self):
- """
- Turn the x- and y-axis on.
- This affects the axis lines, ticks, ticklabels, grid and axis labels.
- """
- self.axison = True
- self.stale = True
- # data limits, ticks, tick labels, and formatting
- 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())
- def xaxis_inverted(self):
- """
- Return whether the x-axis is inverted.
- The axis is inverted if the left value is larger than the right value.
- See Also
- --------
- invert_xaxis
- get_xlim, set_xlim
- get_xbound, set_xbound
- """
- return self.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 axes inversion regardless of parameter order.
- It will not change the autoscaling setting (``Axes._autoscaleXon``).
- Parameters
- ----------
- lower, upper : float or None
- The lower and upper bounds. If *None*, the respective axis bound
- is not modified.
- 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
- if self.xaxis_inverted():
- if lower < upper:
- self.set_xlim(upper, lower, auto=None)
- else:
- self.set_xlim(lower, upper, auto=None)
- else:
- if lower < upper:
- self.set_xlim(lower, upper, auto=None)
- else:
- self.set_xlim(upper, lower, 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
- --------
- set_xlim
- set_xbound, get_xbound
- invert_xaxis, 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, 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: (bottom: float, top: float)
- right : float, optional
- The right xlim in data coordinates. Passing *None* leaves the
- limit unchanged.
- emit : bool, optional
- Whether to notify observers of limit change (default: True).
- auto : bool or None, optional
- Whether to turn on autoscaling of the x-axis. True turns on,
- False turns off (default action), None leaves unchanged.
- xmin, xmax : scalar, 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 `xmin` and `left`')
- left = xmin
- if xmax is not None:
- if right is not None:
- raise TypeError('Cannot pass both `xmax` and `right`')
- right = xmax
- self._process_unit_info(xdata=(left, right))
- left = self._validate_converted_limits(left, self.convert_xunits)
- right = self._validate_converted_limits(right, self.convert_xunits)
- if left is None or right is None:
- # Axes init calls set_xlim(0, 1) before get_xlim() can be called,
- # so only grab the limits if we really need them.
- old_left, old_right = self.get_xlim()
- if left is None:
- left = old_left
- if right is None:
- right = old_right
- if self.get_xscale() == 'log' and (left <= 0 or right <= 0):
- # Axes init calls set_xlim(0, 1) before get_xlim() can be called,
- # so only grab the limits if we really need them.
- old_left, old_right = self.get_xlim()
- if left <= 0:
- cbook._warn_external(
- 'Attempted to set non-positive left xlim on a '
- 'log-scaled axis.\n'
- 'Invalid limit will be ignored.')
- left = old_left
- if right <= 0:
- cbook._warn_external(
- 'Attempted to set non-positive right xlim on a '
- 'log-scaled axis.\n'
- 'Invalid limit will be ignored.')
- right = old_right
- if left == right:
- cbook._warn_external(
- f"Attempting to set identical left == right == {left} results "
- f"in singular transformations; automatically expanding.")
- reverse = left > right
- left, right = self.xaxis.get_major_locator().nonsingular(left, right)
- left, right = self.xaxis.limit_range_for_scale(left, right)
- # cast to bool to avoid bad interaction between python 3.8 and np.bool_
- left, right = sorted([left, right], reverse=bool(reverse))
- self._viewLim.intervalx = (left, right)
- if auto is not None:
- self._autoscaleXon = bool(auto)
- if emit:
- self.callbacks.process('xlim_changed', self)
- # Call all of the other x-axes that are shared with this one
- for other in self._shared_x_axes.get_siblings(self):
- if other is not self:
- other.set_xlim(self.viewLim.intervalx,
- emit=False, auto=auto)
- if other.figure != self.figure:
- other.figure.canvas.draw_idle()
- self.stale = True
- return left, right
- def get_xscale(self):
- """
- Return the x-axis scale as string.
- See Also
- --------
- set_xscale
- """
- return self.xaxis.get_scale()
- def set_xscale(self, value, **kwargs):
- """
- Set the x-axis scale.
- Parameters
- ----------
- value : {"linear", "log", "symlog", "logit", ...}
- The axis scale type to apply.
- **kwargs
- Different keyword arguments are accepted, depending on the scale.
- See the respective class keyword arguments:
- - `matplotlib.scale.LinearScale`
- - `matplotlib.scale.LogScale`
- - `matplotlib.scale.SymmetricalLogScale`
- - `matplotlib.scale.LogitScale`
- Notes
- -----
- By default, Matplotlib supports the above mentioned scales.
- Additionally, custom scales may be registered using
- `matplotlib.scale.register_scale`. These scales can then also
- be used here.
- """
- old_default_lims = (self.xaxis.get_major_locator()
- .nonsingular(-np.inf, np.inf))
- g = self.get_shared_x_axes()
- for ax in g.get_siblings(self):
- ax.xaxis._set_scale(value, **kwargs)
- ax._update_transScale()
- ax.stale = True
- new_default_lims = (self.xaxis.get_major_locator()
- .nonsingular(-np.inf, np.inf))
- if old_default_lims != new_default_lims:
- # Force autoscaling now, to take advantage of the scale locator's
- # nonsingular() before it possibly gets swapped out by the user.
- self.autoscale_view(scaley=False)
- @cbook._make_keyword_only("3.2", "minor")
- def get_xticks(self, minor=False):
- """Return the x ticks as a list of locations"""
- return self.xaxis.get_ticklocs(minor=minor)
- @cbook._make_keyword_only("3.2", "minor")
- def set_xticks(self, ticks, minor=False):
- """
- Set the x ticks with list of *ticks*
- Parameters
- ----------
- ticks : list
- List of x-axis tick locations.
- minor : bool, optional
- If ``False`` sets major ticks, if ``True`` sets minor ticks.
- Default is ``False``.
- """
- ret = self.xaxis.set_ticks(ticks, minor=minor)
- self.stale = True
- return ret
- def get_xmajorticklabels(self):
- """
- Get the major x tick labels.
- Returns
- -------
- labels : list
- List of `~matplotlib.text.Text` instances
- """
- return self.xaxis.get_majorticklabels()
- def get_xminorticklabels(self):
- """
- Get the minor x tick labels.
- Returns
- -------
- labels : list
- List of `~matplotlib.text.Text` instances
- """
- return self.xaxis.get_minorticklabels()
- def get_xticklabels(self, minor=False, which=None):
- """
- Get the x tick labels as a list of `~matplotlib.text.Text` instances.
- Parameters
- ----------
- minor : bool, optional
- If True return the minor ticklabels,
- else return the major ticklabels.
- which : None, ('minor', 'major', 'both')
- Overrides *minor*.
- Selects which ticklabels to return
- Returns
- -------
- ret : list
- List of `~matplotlib.text.Text` instances.
- """
- return self.xaxis.get_ticklabels(minor=minor, which=which)
- def set_xticklabels(self, labels, fontdict=None, minor=False, **kwargs):
- """
- Set the x-tick labels with list of string labels.
- Parameters
- ----------
- labels : List[str]
- List of string labels.
- fontdict : dict, optional
- A dictionary controlling the appearance of the ticklabels.
- The default *fontdict* is::
- {'fontsize': rcParams['axes.titlesize'],
- 'fontweight': rcParams['axes.titleweight'],
- 'verticalalignment': 'baseline',
- 'horizontalalignment': loc}
- minor : bool, optional
- Whether to set the minor ticklabels rather than the major ones.
- Returns
- -------
- A list of `~.text.Text` instances.
- Other Parameters
- -----------------
- **kwargs : `~.text.Text` properties.
- """
- if fontdict is not None:
- kwargs.update(fontdict)
- ret = self.xaxis.set_ticklabels(labels,
- minor=minor, **kwargs)
- self.stale = True
- return ret
- 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())
- def yaxis_inverted(self):
- """
- Return whether the y-axis is inverted.
- The axis is inverted if the bottom value is larger than the top value.
- See Also
- --------
- invert_yaxis
- get_ylim, set_ylim
- get_ybound, set_ybound
- """
- return self.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 axes inversion regardless of parameter order.
- It will not change the autoscaling setting (``Axes._autoscaleYon``).
- Parameters
- ----------
- lower, upper : float or None
- The lower and upper bounds. If *None*, the respective axis bound
- is not modified.
- 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
- if self.yaxis_inverted():
- if lower < upper:
- self.set_ylim(upper, lower, auto=None)
- else:
- self.set_ylim(lower, upper, auto=None)
- else:
- if lower < upper:
- self.set_ylim(lower, upper, auto=None)
- else:
- self.set_ylim(upper, lower, 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
- --------
- set_ylim
- set_ybound, get_ybound
- invert_yaxis, 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, optional
- Whether to notify observers of limit change (default: ``True``).
- auto : bool or None, optional
- Whether to turn on autoscaling of the y-axis. *True* turns on,
- *False* turns off (default action), *None* leaves unchanged.
- ymin, ymax : scalar, 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 `ymin` and `bottom`')
- bottom = ymin
- if ymax is not None:
- if top is not None:
- raise TypeError('Cannot pass both `ymax` and `top`')
- top = ymax
- self._process_unit_info(ydata=(bottom, top))
- bottom = self._validate_converted_limits(bottom, self.convert_yunits)
- top = self._validate_converted_limits(top, self.convert_yunits)
- if bottom is None or top is None:
- # Axes init calls set_ylim(0, 1) before get_ylim() can be called,
- # so only grab the limits if we really need them.
- old_bottom, old_top = self.get_ylim()
- if bottom is None:
- bottom = old_bottom
- if top is None:
- top = old_top
- if self.get_yscale() == 'log' and (bottom <= 0 or top <= 0):
- # Axes init calls set_xlim(0, 1) before get_xlim() can be called,
- # so only grab the limits if we really need them.
- old_bottom, old_top = self.get_ylim()
- if bottom <= 0:
- cbook._warn_external(
- 'Attempted to set non-positive bottom ylim on a '
- 'log-scaled axis.\n'
- 'Invalid limit will be ignored.')
- bottom = old_bottom
- if top <= 0:
- cbook._warn_external(
- 'Attempted to set non-positive top ylim on a '
- 'log-scaled axis.\n'
- 'Invalid limit will be ignored.')
- top = old_top
- if bottom == top:
- cbook._warn_external(
- f"Attempting to set identical bottom == top == {bottom} "
- f"results in singular transformations; automatically "
- f"expanding.")
- reverse = bottom > top
- bottom, top = self.yaxis.get_major_locator().nonsingular(bottom, top)
- bottom, top = self.yaxis.limit_range_for_scale(bottom, top)
- # cast to bool to avoid bad interaction between python 3.8 and np.bool_
- bottom, top = sorted([bottom, top], reverse=bool(reverse))
- self._viewLim.intervaly = (bottom, top)
- if auto is not None:
- self._autoscaleYon = bool(auto)
- if emit:
- self.callbacks.process('ylim_changed', self)
- # Call all of the other y-axes that are shared with this one
- for other in self._shared_y_axes.get_siblings(self):
- if other is not self:
- other.set_ylim(self.viewLim.intervaly,
- emit=False, auto=auto)
- if other.figure != self.figure:
- other.figure.canvas.draw_idle()
- self.stale = True
- return bottom, top
- def get_yscale(self):
- """
- Return the y-axis scale as string.
- See Also
- --------
- set_yscale
- """
- return self.yaxis.get_scale()
- def set_yscale(self, value, **kwargs):
- """
- Set the y-axis scale.
- Parameters
- ----------
- value : {"linear", "log", "symlog", "logit", ...}
- The axis scale type to apply.
- **kwargs
- Different keyword arguments are accepted, depending on the scale.
- See the respective class keyword arguments:
- - `matplotlib.scale.LinearScale`
- - `matplotlib.scale.LogScale`
- - `matplotlib.scale.SymmetricalLogScale`
- - `matplotlib.scale.LogitScale`
- Notes
- -----
- By default, Matplotlib supports the above mentioned scales.
- Additionally, custom scales may be registered using
- `matplotlib.scale.register_scale`. These scales can then also
- be used here.
- """
- old_default_lims = (self.yaxis.get_major_locator()
- .nonsingular(-np.inf, np.inf))
- g = self.get_shared_y_axes()
- for ax in g.get_siblings(self):
- ax.yaxis._set_scale(value, **kwargs)
- ax._update_transScale()
- ax.stale = True
- new_default_lims = (self.yaxis.get_major_locator()
- .nonsingular(-np.inf, np.inf))
- if old_default_lims != new_default_lims:
- # Force autoscaling now, to take advantage of the scale locator's
- # nonsingular() before it possibly gets swapped out by the user.
- self.autoscale_view(scalex=False)
- @cbook._make_keyword_only("3.2", "minor")
- def get_yticks(self, minor=False):
- """Return the y ticks as a list of locations"""
- return self.yaxis.get_ticklocs(minor=minor)
- @cbook._make_keyword_only("3.2", "minor")
- def set_yticks(self, ticks, minor=False):
- """
- Set the y ticks with list of *ticks*
- Parameters
- ----------
- ticks : list
- List of y-axis tick locations
- minor : bool, optional
- If ``False`` sets major ticks, if ``True`` sets minor ticks.
- Default is ``False``.
- """
- ret = self.yaxis.set_ticks(ticks, minor=minor)
- return ret
- def get_ymajorticklabels(self):
- """
- Get the major y tick labels.
- Returns
- -------
- labels : list
- List of `~matplotlib.text.Text` instances
- """
- return self.yaxis.get_majorticklabels()
- def get_yminorticklabels(self):
- """
- Get the minor y tick labels.
- Returns
- -------
- labels : list
- List of `~matplotlib.text.Text` instances
- """
- return self.yaxis.get_minorticklabels()
- def get_yticklabels(self, minor=False, which=None):
- """
- Get the y tick labels as a list of `~matplotlib.text.Text` instances.
- Parameters
- ----------
- minor : bool
- If True return the minor ticklabels,
- else return the major ticklabels
- which : None, ('minor', 'major', 'both')
- Overrides *minor*.
- Selects which ticklabels to return
- Returns
- -------
- ret : list
- List of `~matplotlib.text.Text` instances.
- """
- return self.yaxis.get_ticklabels(minor=minor, which=which)
- def set_yticklabels(self, labels, fontdict=None, minor=False, **kwargs):
- """
- Set the y-tick labels with list of strings labels.
- Parameters
- ----------
- labels : List[str]
- list of string labels
- fontdict : dict, optional
- A dictionary controlling the appearance of the ticklabels.
- The default *fontdict* is::
- {'fontsize': rcParams['axes.titlesize'],
- 'fontweight': rcParams['axes.titleweight'],
- 'verticalalignment': 'baseline',
- 'horizontalalignment': loc}
- minor : bool, optional
- Whether to set the minor ticklabels rather than the major ones.
- Returns
- -------
- A list of `~.text.Text` instances.
- Other Parameters
- ----------------
- **kwargs : `~.text.Text` properties.
- """
- if fontdict is not None:
- kwargs.update(fontdict)
- return self.yaxis.set_ticklabels(labels,
- minor=minor, **kwargs)
- def xaxis_date(self, tz=None):
- """
- Sets up x-axis ticks and labels that treat the x data as dates.
- Parameters
- ----------
- tz : str or `tzinfo` instance, optional
- Timezone. Defaults to :rc:`timezone`.
- """
- # should be enough to inform the unit conversion interface
- # dates are coming in
- self.xaxis.axis_date(tz)
- def yaxis_date(self, tz=None):
- """
- Sets up y-axis ticks and labels that treat the y data as dates.
- Parameters
- ----------
- tz : str or `tzinfo` instance, optional
- Timezone. Defaults to :rc:`timezone`.
- """
- self.yaxis.axis_date(tz)
- 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 an 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."""
- if x is None:
- xs = '???'
- else:
- xs = self.format_xdata(x)
- if y is None:
- ys = '???'
- else:
- ys = self.format_ydata(y)
- return 'x=%s y=%s' % (xs, ys)
- 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 *True* if this axes supports the zoom box button functionality.
- """
- return True
- def can_pan(self):
- """
- Return *True* if 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.
- Called before a view is changed, such as during a pan or zoom
- initiated by the user. You may return any information you deem
- necessary to describe the view.
- .. note::
- Intended to be overridden by new projection types, but if not, the
- default implementation saves the view limits. You *must* implement
- :meth:`_set_view` if you implement this method.
- """
- xmin, xmax = self.get_xlim()
- ymin, ymax = self.get_ylim()
- return (xmin, xmax, ymin, ymax)
- def _set_view(self, view):
- """
- Apply a previously saved view.
- Called when restoring a view, such as with the navigation buttons.
- .. note::
- Intended to be overridden by new projection types, but if not, the
- default implementation restores the view limits. You *must*
- implement :meth:`_get_view` if you implement this method.
- """
- xmin, xmax, ymin, ymax = view
- self.set_xlim((xmin, xmax))
- self.set_ylim((ymin, ymax))
- 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.
- """
- Xmin, Xmax = self.get_xlim()
- Ymin, Ymax = self.get_ylim()
- if len(bbox) == 3:
- # Zooming code
- xp, yp, scl = bbox
- # Should not happen
- if scl == 0:
- scl = 1.
- # direction = 'in'
- if scl > 1:
- direction = 'in'
- else:
- direction = 'out'
- scl = 1/scl
- # get the limits of the axes
- tranD2C = self.transData.transform
- xmin, ymin = tranD2C((Xmin, Ymin))
- xmax, ymax = tranD2C((Xmax, Ymax))
- # 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
- cbook._warn_external(
- "Warning in _set_view_from_bbox: bounding box is not a tuple "
- "of length 3 or 4. Ignoring the view change.")
- return
- # Just grab bounding box
- lastx, lasty, x, y = bbox
- # zoom to rect
- inverse = self.transData.inverted()
- (lastx, lasty), (x, y) = inverse.transform([(lastx, lasty), (x, y)])
- if twinx:
- x0, x1 = Xmin, Xmax
- else:
- if Xmin < Xmax:
- if x < lastx:
- x0, x1 = x, lastx
- else:
- x0, x1 = lastx, x
- if x0 < Xmin:
- x0 = Xmin
- if x1 > Xmax:
- x1 = Xmax
- else:
- if x > lastx:
- x0, x1 = x, lastx
- else:
- x0, x1 = lastx, x
- if x0 > Xmin:
- x0 = Xmin
- if x1 < Xmax:
- x1 = Xmax
- if twiny:
- y0, y1 = Ymin, Ymax
- else:
- if Ymin < Ymax:
- if y < lasty:
- y0, y1 = y, lasty
- else:
- y0, y1 = lasty, y
- if y0 < Ymin:
- y0 = Ymin
- if y1 > Ymax:
- y1 = Ymax
- else:
- if y > lasty:
- y0, y1 = y, lasty
- else:
- y0, y1 = lasty, y
- if y0 > Ymin:
- y0 = Ymin
- if y1 < Ymax:
- y1 = Ymax
- if direction == 'in':
- if mode == 'x':
- self.set_xlim((x0, x1))
- elif mode == 'y':
- self.set_ylim((y0, y1))
- else:
- self.set_xlim((x0, x1))
- self.set_ylim((y0, y1))
- elif direction == 'out':
- if self.get_xscale() == 'log':
- alpha = np.log(Xmax / Xmin) / np.log(x1 / x0)
- rx1 = pow(Xmin / x0, alpha) * Xmin
- rx2 = pow(Xmax / x0, alpha) * Xmin
- else:
- alpha = (Xmax - Xmin) / (x1 - x0)
- rx1 = alpha * (Xmin - x0) + Xmin
- rx2 = alpha * (Xmax - x0) + Xmin
- if self.get_yscale() == 'log':
- alpha = np.log(Ymax / Ymin) / np.log(y1 / y0)
- ry1 = pow(Ymin / y0, alpha) * Ymin
- ry2 = pow(Ymax / y0, alpha) * Ymin
- else:
- alpha = (Ymax - Ymin) / (y1 - y0)
- ry1 = alpha * (Ymin - y0) + Ymin
- ry2 = alpha * (Ymax - y0) + Ymin
- if mode == 'x':
- self.set_xlim((rx1, rx2))
- elif mode == 'y':
- self.set_ylim((ry1, ry2))
- else:
- self.set_xlim((rx1, rx2))
- self.set_ylim((ry1, ry2))
- def start_pan(self, x, y, button):
- """
- Called when a pan operation has started.
- *x*, *y* are the mouse coordinates in display coords.
- button is the mouse button number:
- * 1: LEFT
- * 2: MIDDLE
- * 3: RIGHT
- .. note::
- 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.)
- .. note::
- Intended to be overridden by new projection types.
- """
- del self._pan_start
- def drag_pan(self, button, key, x, y):
- """
- Called when the mouse moves during a pan operation.
- *button* is the mouse button number:
- * 1: LEFT
- * 2: MIDDLE
- * 3: RIGHT
- *key* is a "shift" key
- *x*, *y* are the mouse coordinates in display coords.
- .. note::
- Intended to be overridden by new projection types.
- """
- 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:
- cbook._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
- self.set_xlim(points[:, 0])
- self.set_ylim(points[:, 1])
- def get_children(self):
- # docstring inherited.
- return [
- *self.collections,
- *self.patches,
- *self.lines,
- *self.texts,
- *self.artists,
- *self.spines.values(),
- *self._get_axis_list(),
- self.title, self._left_title, self._right_title,
- *self.tables,
- *self.images,
- *self.child_axes,
- *([self.legend_] if self.legend_ is not None else []),
- self.patch,
- ]
- def contains(self, mouseevent):
- # docstring inherited.
- inside, info = self._default_contains(mouseevent)
- if inside is not None:
- return inside, info
- 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()
- 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)
- if not self.axison:
- for _axis in self._get_axis_list():
- artists.remove(_axis)
- return [artist for artist in artists
- if (artist.get_visible() and artist.get_in_layout())]
- def get_tightbbox(self, renderer, call_axes_locator=True,
- bbox_extra_artists=None):
- """
- 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` instance
- 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 : boolean (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.
- Returns
- -------
- bbox : `.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 not self.get_visible():
- return None
- locator = self.get_axes_locator()
- if locator and call_axes_locator:
- pos = locator(self, renderer)
- self.apply_aspect(pos)
- else:
- self.apply_aspect()
- if self.axison:
- bb_xaxis = self.xaxis.get_tightbbox(renderer)
- if bb_xaxis:
- bb.append(bb_xaxis)
- bb_yaxis = self.yaxis.get_tightbbox(renderer)
- if bb_yaxis:
- bb.append(bb_yaxis)
- self._update_title_position(renderer)
- axbbox = self.get_window_extent(renderer)
- bb.append(axbbox)
- self._update_title_position(renderer)
- if self.title.get_visible():
- bb.append(self.title.get_window_extent(renderer))
- if self._left_title.get_visible():
- bb.append(self._left_title.get_window_extent(renderer))
- if self._right_title.get_visible():
- bb.append(self._right_title.get_window_extent(renderer))
- bb.append(self.get_window_extent(renderer))
- bbox_artists = bbox_extra_artists
- if bbox_artists is None:
- bbox_artists = self.get_default_bbox_extra_artists()
- for a in bbox_artists:
- # Extra check here to quickly see if clipping is on and
- # contained in the axes. If it is, don't get the tightbbox for
- # this artist because this can be expensive:
- clip_extent = a._get_clipping_extent_bbox()
- if clip_extent is not None:
- clip_extent = mtransforms.Bbox.intersection(clip_extent,
- axbbox)
- if np.all(clip_extent.extents == axbbox.extents):
- # clip extent is inside the axes bbox so don't check
- # this artist
- continue
- 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)
- _bbox = mtransforms.Bbox.union(
- [b for b in bb if b.width != 0 or b.height != 0])
- return _bbox
- def _make_twin_axes(self, *args, **kwargs):
- """Make a twinx axes of self. This is used for twinx and twiny."""
- # Typically, SubplotBase._make_twin_axes is called instead of this.
- if 'sharex' in kwargs and 'sharey' in kwargs:
- raise ValueError("Twinned Axes may share only one axis")
- ax2 = self.figure.add_axes(self.get_position(True), *args, **kwargs)
- self.set_adjustable('datalim')
- ax2.set_adjustable('datalim')
- self._twinned_axes.join(self, ax2)
- return ax2
- 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
- -------
- ax_twin : 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)
- 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
- -------
- ax_twin : 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)
- return ax2
- def get_shared_x_axes(self):
- """Return a reference to the shared axes Grouper object for x axes."""
- return self._shared_x_axes
- def get_shared_y_axes(self):
- """Return a reference to the shared axes Grouper object for y axes."""
- return self._shared_y_axes
|