123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579 |
- """
- Abstract base classes define the primitives that renderers and
- graphics contexts must implement to serve as a Matplotlib backend.
- `RendererBase`
- An abstract base class to handle drawing/rendering operations.
- `FigureCanvasBase`
- The abstraction layer that separates the `.Figure` from the backend
- specific details like a user interface drawing area.
- `GraphicsContextBase`
- An abstract base class that provides color, line styles, etc.
- `Event`
- The base class for all of the Matplotlib event handling. Derived classes
- such as `KeyEvent` and `MouseEvent` store the meta data like keys and
- buttons pressed, x and y locations in pixel and `~.axes.Axes` coordinates.
- `ShowBase`
- The base class for the ``Show`` class of each interactive backend; the
- 'show' callable is then set to ``Show.__call__``.
- `ToolContainerBase`
- The base class for the Toolbar class of each interactive backend.
- """
- from contextlib import contextmanager, suppress
- from enum import Enum, IntEnum
- import functools
- import importlib
- import inspect
- import io
- import logging
- import os
- import re
- import sys
- import time
- import traceback
- from weakref import WeakKeyDictionary
- import numpy as np
- import matplotlib as mpl
- from matplotlib import (
- backend_tools as tools, cbook, colors, textpath, tight_bbox,
- transforms, widgets, get_backend, is_interactive, rcParams)
- from matplotlib._pylab_helpers import Gcf
- from matplotlib.backend_managers import ToolManager
- from matplotlib.transforms import Affine2D
- from matplotlib.path import Path
- from matplotlib.cbook import _setattr_cm
- _log = logging.getLogger(__name__)
- _default_filetypes = {
- 'eps': 'Encapsulated Postscript',
- 'jpg': 'Joint Photographic Experts Group',
- 'jpeg': 'Joint Photographic Experts Group',
- 'pdf': 'Portable Document Format',
- 'pgf': 'PGF code for LaTeX',
- 'png': 'Portable Network Graphics',
- 'ps': 'Postscript',
- 'raw': 'Raw RGBA bitmap',
- 'rgba': 'Raw RGBA bitmap',
- 'svg': 'Scalable Vector Graphics',
- 'svgz': 'Scalable Vector Graphics',
- 'tif': 'Tagged Image File Format',
- 'tiff': 'Tagged Image File Format',
- }
- _default_backends = {
- 'eps': 'matplotlib.backends.backend_ps',
- 'jpg': 'matplotlib.backends.backend_agg',
- 'jpeg': 'matplotlib.backends.backend_agg',
- 'pdf': 'matplotlib.backends.backend_pdf',
- 'pgf': 'matplotlib.backends.backend_pgf',
- 'png': 'matplotlib.backends.backend_agg',
- 'ps': 'matplotlib.backends.backend_ps',
- 'raw': 'matplotlib.backends.backend_agg',
- 'rgba': 'matplotlib.backends.backend_agg',
- 'svg': 'matplotlib.backends.backend_svg',
- 'svgz': 'matplotlib.backends.backend_svg',
- 'tif': 'matplotlib.backends.backend_agg',
- 'tiff': 'matplotlib.backends.backend_agg',
- }
- def register_backend(format, backend, description=None):
- """
- Register a backend for saving to a given file format.
- Parameters
- ----------
- format : str
- File extension
- backend : module string or canvas class
- Backend for handling file output
- description : str, default: ""
- Description of the file type.
- """
- if description is None:
- description = ''
- _default_backends[format] = backend
- _default_filetypes[format] = description
- def get_registered_canvas_class(format):
- """
- Return the registered default canvas for given file format.
- Handles deferred import of required backend.
- """
- if format not in _default_backends:
- return None
- backend_class = _default_backends[format]
- if isinstance(backend_class, str):
- backend_class = importlib.import_module(backend_class).FigureCanvas
- _default_backends[format] = backend_class
- return backend_class
- class RendererBase:
- """
- An abstract base class to handle drawing/rendering operations.
- The following methods must be implemented in the backend for full
- functionality (though just implementing :meth:`draw_path` alone would
- give a highly capable backend):
- * :meth:`draw_path`
- * :meth:`draw_image`
- * :meth:`draw_gouraud_triangle`
- The following methods *should* be implemented in the backend for
- optimization reasons:
- * :meth:`draw_text`
- * :meth:`draw_markers`
- * :meth:`draw_path_collection`
- * :meth:`draw_quad_mesh`
- """
- def __init__(self):
- super().__init__()
- self._texmanager = None
- self._text2path = textpath.TextToPath()
- def open_group(self, s, gid=None):
- """
- Open a grouping element with label *s* and *gid* (if set) as id.
- Only used by the SVG renderer.
- """
- def close_group(self, s):
- """
- Close a grouping element with label *s*.
- Only used by the SVG renderer.
- """
- def draw_path(self, gc, path, transform, rgbFace=None):
- """Draw a `~.path.Path` instance using the given affine transform."""
- raise NotImplementedError
- def draw_markers(self, gc, marker_path, marker_trans, path,
- trans, rgbFace=None):
- """
- Draw a marker at each of the vertices in path.
- This includes all vertices, including control points on curves.
- To avoid that behavior, those vertices should be removed before
- calling this function.
- This provides a fallback implementation of draw_markers that
- makes multiple calls to :meth:`draw_path`. Some backends may
- want to override this method in order to draw the marker only
- once and reuse it multiple times.
- Parameters
- ----------
- gc : `.GraphicsContextBase`
- The graphics context.
- marker_trans : `matplotlib.transforms.Transform`
- An affine transform applied to the marker.
- trans : `matplotlib.transforms.Transform`
- An affine transform applied to the path.
- """
- for vertices, codes in path.iter_segments(trans, simplify=False):
- if len(vertices):
- x, y = vertices[-2:]
- self.draw_path(gc, marker_path,
- marker_trans +
- transforms.Affine2D().translate(x, y),
- rgbFace)
- def draw_path_collection(self, gc, master_transform, paths, all_transforms,
- offsets, offsetTrans, facecolors, edgecolors,
- linewidths, linestyles, antialiaseds, urls,
- offset_position):
- """
- Draw a collection of paths selecting drawing properties from
- the lists *facecolors*, *edgecolors*, *linewidths*,
- *linestyles* and *antialiaseds*. *offsets* is a list of
- offsets to apply to each of the paths. The offsets in
- *offsets* are first transformed by *offsetTrans* before being
- applied.
- *offset_position* may be either "screen" or "data" depending on the
- space that the offsets are in; "data" is deprecated.
- This provides a fallback implementation of
- :meth:`draw_path_collection` that makes multiple calls to
- :meth:`draw_path`. Some backends may want to override this in
- order to render each set of path data only once, and then
- reference that path multiple times with the different offsets,
- colors, styles etc. The generator methods
- :meth:`_iter_collection_raw_paths` and
- :meth:`_iter_collection` are provided to help with (and
- standardize) the implementation across backends. It is highly
- recommended to use those generators, so that changes to the
- behavior of :meth:`draw_path_collection` can be made globally.
- """
- path_ids = self._iter_collection_raw_paths(master_transform,
- paths, all_transforms)
- for xo, yo, path_id, gc0, rgbFace in self._iter_collection(
- gc, master_transform, all_transforms, list(path_ids), offsets,
- offsetTrans, facecolors, edgecolors, linewidths, linestyles,
- antialiaseds, urls, offset_position):
- path, transform = path_id
- # Only apply another translation if we have an offset, else we
- # resuse the inital transform.
- if xo != 0 or yo != 0:
- # The transformation can be used by multiple paths. Since
- # translate is a inplace operation, we need to copy the
- # transformation by .frozen() before applying the translation.
- transform = transform.frozen()
- transform.translate(xo, yo)
- self.draw_path(gc0, path, transform, rgbFace)
- def draw_quad_mesh(self, gc, master_transform, meshWidth, meshHeight,
- coordinates, offsets, offsetTrans, facecolors,
- antialiased, edgecolors):
- """
- Fallback implementation of :meth:`draw_quad_mesh` that generates paths
- and then calls :meth:`draw_path_collection`.
- """
- from matplotlib.collections import QuadMesh
- paths = QuadMesh.convert_mesh_to_paths(
- meshWidth, meshHeight, coordinates)
- if edgecolors is None:
- edgecolors = facecolors
- linewidths = np.array([gc.get_linewidth()], float)
- return self.draw_path_collection(
- gc, master_transform, paths, [], offsets, offsetTrans, facecolors,
- edgecolors, linewidths, [], [antialiased], [None], 'screen')
- def draw_gouraud_triangle(self, gc, points, colors, transform):
- """
- Draw a Gouraud-shaded triangle.
- Parameters
- ----------
- gc : `.GraphicsContextBase`
- The graphics context.
- points : array-like, shape=(3, 2)
- Array of (x, y) points for the triangle.
- colors : array-like, shape=(3, 4)
- RGBA colors for each point of the triangle.
- transform : `matplotlib.transforms.Transform`
- An affine transform to apply to the points.
- """
- raise NotImplementedError
- def draw_gouraud_triangles(self, gc, triangles_array, colors_array,
- transform):
- """
- Draw a series of Gouraud triangles.
- Parameters
- ----------
- points : array-like, shape=(N, 3, 2)
- Array of *N* (x, y) points for the triangles.
- colors : array-like, shape=(N, 3, 4)
- Array of *N* RGBA colors for each point of the triangles.
- transform : `matplotlib.transforms.Transform`
- An affine transform to apply to the points.
- """
- transform = transform.frozen()
- for tri, col in zip(triangles_array, colors_array):
- self.draw_gouraud_triangle(gc, tri, col, transform)
- def _iter_collection_raw_paths(self, master_transform, paths,
- all_transforms):
- """
- Helper method (along with :meth:`_iter_collection`) to implement
- :meth:`draw_path_collection` in a space-efficient manner.
- This method yields all of the base path/transform
- combinations, given a master transform, a list of paths and
- list of transforms.
- The arguments should be exactly what is passed in to
- :meth:`draw_path_collection`.
- The backend should take each yielded path and transform and
- create an object that can be referenced (reused) later.
- """
- Npaths = len(paths)
- Ntransforms = len(all_transforms)
- N = max(Npaths, Ntransforms)
- if Npaths == 0:
- return
- transform = transforms.IdentityTransform()
- for i in range(N):
- path = paths[i % Npaths]
- if Ntransforms:
- transform = Affine2D(all_transforms[i % Ntransforms])
- yield path, transform + master_transform
- def _iter_collection_uses_per_path(self, paths, all_transforms,
- offsets, facecolors, edgecolors):
- """
- Compute how many times each raw path object returned by
- _iter_collection_raw_paths would be used when calling
- _iter_collection. This is intended for the backend to decide
- on the tradeoff between using the paths in-line and storing
- them once and reusing. Rounds up in case the number of uses
- is not the same for every path.
- """
- Npaths = len(paths)
- if Npaths == 0 or len(facecolors) == len(edgecolors) == 0:
- return 0
- Npath_ids = max(Npaths, len(all_transforms))
- N = max(Npath_ids, len(offsets))
- return (N + Npath_ids - 1) // Npath_ids
- def _iter_collection(self, gc, master_transform, all_transforms,
- path_ids, offsets, offsetTrans, facecolors,
- edgecolors, linewidths, linestyles,
- antialiaseds, urls, offset_position):
- """
- Helper method (along with :meth:`_iter_collection_raw_paths`) to
- implement :meth:`draw_path_collection` in a space-efficient manner.
- This method yields all of the path, offset and graphics
- context combinations to draw the path collection. The caller
- should already have looped over the results of
- :meth:`_iter_collection_raw_paths` to draw this collection.
- The arguments should be the same as that passed into
- :meth:`draw_path_collection`, with the exception of
- *path_ids*, which is a list of arbitrary objects that the
- backend will use to reference one of the paths created in the
- :meth:`_iter_collection_raw_paths` stage.
- Each yielded result is of the form::
- xo, yo, path_id, gc, rgbFace
- where *xo*, *yo* is an offset; *path_id* is one of the elements of
- *path_ids*; *gc* is a graphics context and *rgbFace* is a color to
- use for filling the path.
- """
- Ntransforms = len(all_transforms)
- Npaths = len(path_ids)
- Noffsets = len(offsets)
- N = max(Npaths, Noffsets)
- Nfacecolors = len(facecolors)
- Nedgecolors = len(edgecolors)
- Nlinewidths = len(linewidths)
- Nlinestyles = len(linestyles)
- Naa = len(antialiaseds)
- Nurls = len(urls)
- if offset_position == "data":
- cbook.warn_deprecated(
- "3.3", message="Support for offset_position='data' is "
- "deprecated since %(since)s and will be removed %(removal)s.")
- if (Nfacecolors == 0 and Nedgecolors == 0) or Npaths == 0:
- return
- if Noffsets:
- toffsets = offsetTrans.transform(offsets)
- gc0 = self.new_gc()
- gc0.copy_properties(gc)
- if Nfacecolors == 0:
- rgbFace = None
- if Nedgecolors == 0:
- gc0.set_linewidth(0.0)
- xo, yo = 0, 0
- for i in range(N):
- path_id = path_ids[i % Npaths]
- if Noffsets:
- xo, yo = toffsets[i % Noffsets]
- if offset_position == 'data':
- if Ntransforms:
- transform = (
- Affine2D(all_transforms[i % Ntransforms]) +
- master_transform)
- else:
- transform = master_transform
- (xo, yo), (xp, yp) = transform.transform(
- [(xo, yo), (0, 0)])
- xo = -(xp - xo)
- yo = -(yp - yo)
- if not (np.isfinite(xo) and np.isfinite(yo)):
- continue
- if Nfacecolors:
- rgbFace = facecolors[i % Nfacecolors]
- if Nedgecolors:
- if Nlinewidths:
- gc0.set_linewidth(linewidths[i % Nlinewidths])
- if Nlinestyles:
- gc0.set_dashes(*linestyles[i % Nlinestyles])
- fg = edgecolors[i % Nedgecolors]
- if len(fg) == 4:
- if fg[3] == 0.0:
- gc0.set_linewidth(0)
- else:
- gc0.set_foreground(fg)
- else:
- gc0.set_foreground(fg)
- if rgbFace is not None and len(rgbFace) == 4:
- if rgbFace[3] == 0:
- rgbFace = None
- gc0.set_antialiased(antialiaseds[i % Naa])
- if Nurls:
- gc0.set_url(urls[i % Nurls])
- yield xo, yo, path_id, gc0, rgbFace
- gc0.restore()
- def get_image_magnification(self):
- """
- Get the factor by which to magnify images passed to :meth:`draw_image`.
- Allows a backend to have images at a different resolution to other
- artists.
- """
- return 1.0
- def draw_image(self, gc, x, y, im, transform=None):
- """
- Draw an RGBA image.
- Parameters
- ----------
- gc : `.GraphicsContextBase`
- A graphics context with clipping information.
- x : scalar
- The distance in physical units (i.e., dots or pixels) from the left
- hand side of the canvas.
- y : scalar
- The distance in physical units (i.e., dots or pixels) from the
- bottom side of the canvas.
- im : array-like, shape=(N, M, 4), dtype=np.uint8
- An array of RGBA pixels.
- transform : `matplotlib.transforms.Affine2DBase`
- If and only if the concrete backend is written such that
- :meth:`option_scale_image` returns ``True``, an affine
- transformation (i.e., an `.Affine2DBase`) *may* be passed to
- :meth:`draw_image`. The translation vector of the transformation
- is given in physical units (i.e., dots or pixels). Note that
- the transformation does not override *x* and *y*, and has to be
- applied *before* translating the result by *x* and *y* (this can
- be accomplished by adding *x* and *y* to the translation vector
- defined by *transform*).
- """
- raise NotImplementedError
- def option_image_nocomposite(self):
- """
- Return whether image composition by Matplotlib should be skipped.
- Raster backends should usually return False (letting the C-level
- rasterizer take care of image composition); vector backends should
- usually return ``not rcParams["image.composite_image"]``.
- """
- return False
- def option_scale_image(self):
- """
- Return whether arbitrary affine transformations in :meth:`draw_image`
- are supported (True for most vector backends).
- """
- return False
- @cbook._delete_parameter("3.3", "ismath")
- def draw_tex(self, gc, x, y, s, prop, angle, ismath='TeX!', mtext=None):
- """
- """
- self._draw_text_as_path(gc, x, y, s, prop, angle, ismath="TeX")
- def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None):
- """
- Draw the text instance.
- Parameters
- ----------
- gc : `.GraphicsContextBase`
- The graphics context.
- x : float
- The x location of the text in display coords.
- y : float
- The y location of the text baseline in display coords.
- s : str
- The text string.
- prop : `matplotlib.font_manager.FontProperties`
- The font properties.
- angle : float
- The rotation angle in degrees anti-clockwise.
- mtext : `matplotlib.text.Text`
- The original text object to be rendered.
- Notes
- -----
- **Note for backend implementers:**
- When you are trying to determine if you have gotten your bounding box
- right (which is what enables the text layout/alignment to work
- properly), it helps to change the line in text.py::
- if 0: bbox_artist(self, renderer)
- to if 1, and then the actual bounding box will be plotted along with
- your text.
- """
- self._draw_text_as_path(gc, x, y, s, prop, angle, ismath)
- def _get_text_path_transform(self, x, y, s, prop, angle, ismath):
- """
- Return the text path and transform.
- Parameters
- ----------
- prop : `matplotlib.font_manager.FontProperties`
- The font property.
- s : str
- The text to be converted.
- ismath : bool or "TeX"
- If True, use mathtext parser. If "TeX", use *usetex* mode.
- """
- text2path = self._text2path
- fontsize = self.points_to_pixels(prop.get_size_in_points())
- verts, codes = text2path.get_text_path(prop, s, ismath=ismath)
- path = Path(verts, codes)
- angle = np.deg2rad(angle)
- if self.flipy():
- width, height = self.get_canvas_width_height()
- transform = (Affine2D()
- .scale(fontsize / text2path.FONT_SCALE)
- .rotate(angle)
- .translate(x, height - y))
- else:
- transform = (Affine2D()
- .scale(fontsize / text2path.FONT_SCALE)
- .rotate(angle)
- .translate(x, y))
- return path, transform
- def _draw_text_as_path(self, gc, x, y, s, prop, angle, ismath):
- """
- Draw the text by converting them to paths using textpath module.
- Parameters
- ----------
- prop : `matplotlib.font_manager.FontProperties`
- The font property.
- s : str
- The text to be converted.
- usetex : bool
- Whether to use usetex mode.
- ismath : bool or "TeX"
- If True, use mathtext parser. If "TeX", use *usetex* mode.
- """
- path, transform = self._get_text_path_transform(
- x, y, s, prop, angle, ismath)
- color = gc.get_rgb()
- gc.set_linewidth(0.0)
- self.draw_path(gc, path, transform, rgbFace=color)
- def get_text_width_height_descent(self, s, prop, ismath):
- """
- Get the width, height, and descent (offset from the bottom
- to the baseline), in display coords, of the string *s* with
- `.FontProperties` *prop*.
- """
- if ismath == 'TeX':
- # todo: handle props
- texmanager = self._text2path.get_texmanager()
- fontsize = prop.get_size_in_points()
- w, h, d = texmanager.get_text_width_height_descent(
- s, fontsize, renderer=self)
- return w, h, d
- dpi = self.points_to_pixels(72)
- if ismath:
- dims = self._text2path.mathtext_parser.parse(s, dpi, prop)
- return dims[0:3] # return width, height, descent
- flags = self._text2path._get_hinting_flag()
- font = self._text2path._get_font(prop)
- size = prop.get_size_in_points()
- font.set_size(size, dpi)
- # the width and height of unrotated string
- font.set_text(s, 0.0, flags=flags)
- w, h = font.get_width_height()
- d = font.get_descent()
- w /= 64.0 # convert from subpixels
- h /= 64.0
- d /= 64.0
- return w, h, d
- def flipy(self):
- """
- Return whether y values increase from top to bottom.
- Note that this only affects drawing of texts and images.
- """
- return True
- def get_canvas_width_height(self):
- """Return the canvas width and height in display coords."""
- return 1, 1
- def get_texmanager(self):
- """Return the `.TexManager` instance."""
- if self._texmanager is None:
- from matplotlib.texmanager import TexManager
- self._texmanager = TexManager()
- return self._texmanager
- def new_gc(self):
- """Return an instance of a `.GraphicsContextBase`."""
- return GraphicsContextBase()
- def points_to_pixels(self, points):
- """
- Convert points to display units.
- You need to override this function (unless your backend
- doesn't have a dpi, e.g., postscript or svg). Some imaging
- systems assume some value for pixels per inch::
- points to pixels = points * pixels_per_inch/72 * dpi/72
- Parameters
- ----------
- points : float or array-like
- a float or a numpy array of float
- Returns
- -------
- Points converted to pixels
- """
- return points
- def start_rasterizing(self):
- """
- Switch to the raster renderer.
- Used by `.MixedModeRenderer`.
- """
- def stop_rasterizing(self):
- """
- Switch back to the vector renderer and draw the contents of the raster
- renderer as an image on the vector renderer.
- Used by `.MixedModeRenderer`.
- """
- def start_filter(self):
- """
- Switch to a temporary renderer for image filtering effects.
- Currently only supported by the agg renderer.
- """
- def stop_filter(self, filter_func):
- """
- Switch back to the original renderer. The contents of the temporary
- renderer is processed with the *filter_func* and is drawn on the
- original renderer as an image.
- Currently only supported by the agg renderer.
- """
- def _draw_disabled(self):
- """
- Context manager to temporary disable drawing.
- This is used for getting the drawn size of Artists. This lets us
- run the draw process to update any Python state but does not pay the
- cost of the draw_XYZ calls on the canvas.
- """
- no_ops = {
- meth_name: lambda *args, **kwargs: None
- for meth_name in dir(RendererBase)
- if (meth_name.startswith("draw_")
- or meth_name in ["open_group", "close_group"])
- }
- return _setattr_cm(self, **no_ops)
- class GraphicsContextBase:
- """An abstract base class that provides color, line styles, etc."""
- def __init__(self):
- self._alpha = 1.0
- self._forced_alpha = False # if True, _alpha overrides A from RGBA
- self._antialiased = 1 # use 0, 1 not True, False for extension code
- self._capstyle = 'butt'
- self._cliprect = None
- self._clippath = None
- self._dashes = 0, None
- self._joinstyle = 'round'
- self._linestyle = 'solid'
- self._linewidth = 1
- self._rgb = (0.0, 0.0, 0.0, 1.0)
- self._hatch = None
- self._hatch_color = colors.to_rgba(rcParams['hatch.color'])
- self._hatch_linewidth = rcParams['hatch.linewidth']
- self._url = None
- self._gid = None
- self._snap = None
- self._sketch = None
- def copy_properties(self, gc):
- """Copy properties from *gc* to self."""
- self._alpha = gc._alpha
- self._forced_alpha = gc._forced_alpha
- self._antialiased = gc._antialiased
- self._capstyle = gc._capstyle
- self._cliprect = gc._cliprect
- self._clippath = gc._clippath
- self._dashes = gc._dashes
- self._joinstyle = gc._joinstyle
- self._linestyle = gc._linestyle
- self._linewidth = gc._linewidth
- self._rgb = gc._rgb
- self._hatch = gc._hatch
- self._hatch_color = gc._hatch_color
- self._hatch_linewidth = gc._hatch_linewidth
- self._url = gc._url
- self._gid = gc._gid
- self._snap = gc._snap
- self._sketch = gc._sketch
- def restore(self):
- """
- Restore the graphics context from the stack - needed only
- for backends that save graphics contexts on a stack.
- """
- def get_alpha(self):
- """
- Return the alpha value used for blending - not supported on all
- backends.
- """
- return self._alpha
- def get_antialiased(self):
- """Return whether the object should try to do antialiased rendering."""
- return self._antialiased
- def get_capstyle(self):
- """
- Return the capstyle as a string in ('butt', 'round', 'projecting').
- """
- return self._capstyle
- def get_clip_rectangle(self):
- """
- Return the clip rectangle as a `~matplotlib.transforms.Bbox` instance.
- """
- return self._cliprect
- def get_clip_path(self):
- """
- Return the clip path in the form (path, transform), where path
- is a `~.path.Path` instance, and transform is
- an affine transform to apply to the path before clipping.
- """
- if self._clippath is not None:
- return self._clippath.get_transformed_path_and_affine()
- return None, None
- def get_dashes(self):
- """
- Return the dash style as an (offset, dash-list) pair.
- The dash list is a even-length list that gives the ink on, ink off in
- points. See p. 107 of to PostScript `blue book`_ for more info.
- Default value is (None, None).
- .. _blue book: https://www-cdf.fnal.gov/offline/PostScript/BLUEBOOK.PDF
- """
- return self._dashes
- def get_forced_alpha(self):
- """
- Return whether the value given by get_alpha() should be used to
- override any other alpha-channel values.
- """
- return self._forced_alpha
- def get_joinstyle(self):
- """Return the line join style as one of ('miter', 'round', 'bevel')."""
- return self._joinstyle
- def get_linewidth(self):
- """Return the line width in points."""
- return self._linewidth
- def get_rgb(self):
- """Return a tuple of three or four floats from 0-1."""
- return self._rgb
- def get_url(self):
- """Return a url if one is set, None otherwise."""
- return self._url
- def get_gid(self):
- """Return the object identifier if one is set, None otherwise."""
- return self._gid
- def get_snap(self):
- """
- Return the snap setting, which can be:
- * True: snap vertices to the nearest pixel center
- * False: leave vertices as-is
- * None: (auto) If the path contains only rectilinear line segments,
- round to the nearest pixel center
- """
- return self._snap
- def set_alpha(self, alpha):
- """
- Set the alpha value used for blending - not supported on all backends.
- If ``alpha=None`` (the default), the alpha components of the
- foreground and fill colors will be used to set their respective
- transparencies (where applicable); otherwise, ``alpha`` will override
- them.
- """
- if alpha is not None:
- self._alpha = alpha
- self._forced_alpha = True
- else:
- self._alpha = 1.0
- self._forced_alpha = False
- self.set_foreground(self._rgb, isRGBA=True)
- def set_antialiased(self, b):
- """Set whether object should be drawn with antialiased rendering."""
- # Use ints to make life easier on extension code trying to read the gc.
- self._antialiased = int(bool(b))
- def set_capstyle(self, cs):
- """Set the capstyle to be one of ('butt', 'round', 'projecting')."""
- cbook._check_in_list(['butt', 'round', 'projecting'], cs=cs)
- self._capstyle = cs
- def set_clip_rectangle(self, rectangle):
- """
- Set the clip rectangle with sequence (left, bottom, width, height)
- """
- self._cliprect = rectangle
- def set_clip_path(self, path):
- """
- Set the clip path and transformation.
- Parameters
- ----------
- path : `~matplotlib.transforms.TransformedPath` or None
- """
- cbook._check_isinstance((transforms.TransformedPath, None), path=path)
- self._clippath = path
- def set_dashes(self, dash_offset, dash_list):
- """
- Set the dash style for the gc.
- Parameters
- ----------
- dash_offset : float or None
- The offset (usually 0).
- dash_list : array-like or None
- The on-off sequence as points.
- Notes
- -----
- ``(None, None)`` specifies a solid line.
- See p. 107 of to PostScript `blue book`_ for more info.
- .. _blue book: https://www-cdf.fnal.gov/offline/PostScript/BLUEBOOK.PDF
- """
- if dash_list is not None:
- dl = np.asarray(dash_list)
- if np.any(dl < 0.0):
- raise ValueError(
- "All values in the dash list must be positive")
- self._dashes = dash_offset, dash_list
- def set_foreground(self, fg, isRGBA=False):
- """
- Set the foreground color.
- Parameters
- ----------
- fg : color
- isRGBA : bool
- If *fg* is known to be an ``(r, g, b, a)`` tuple, *isRGBA* can be
- set to True to improve performance.
- """
- if self._forced_alpha and isRGBA:
- self._rgb = fg[:3] + (self._alpha,)
- elif self._forced_alpha:
- self._rgb = colors.to_rgba(fg, self._alpha)
- elif isRGBA:
- self._rgb = fg
- else:
- self._rgb = colors.to_rgba(fg)
- def set_joinstyle(self, js):
- """Set the join style to be one of ('miter', 'round', 'bevel')."""
- cbook._check_in_list(['miter', 'round', 'bevel'], js=js)
- self._joinstyle = js
- def set_linewidth(self, w):
- """Set the linewidth in points."""
- self._linewidth = float(w)
- def set_url(self, url):
- """Set the url for links in compatible backends."""
- self._url = url
- def set_gid(self, id):
- """Set the id."""
- self._gid = id
- def set_snap(self, snap):
- """
- Set the snap setting which may be:
- * True: snap vertices to the nearest pixel center
- * False: leave vertices as-is
- * None: (auto) If the path contains only rectilinear line segments,
- round to the nearest pixel center
- """
- self._snap = snap
- def set_hatch(self, hatch):
- """Set the hatch style (for fills)."""
- self._hatch = hatch
- def get_hatch(self):
- """Get the current hatch style."""
- return self._hatch
- def get_hatch_path(self, density=6.0):
- """Return a `.Path` for the current hatch."""
- hatch = self.get_hatch()
- if hatch is None:
- return None
- return Path.hatch(hatch, density)
- def get_hatch_color(self):
- """Get the hatch color."""
- return self._hatch_color
- def set_hatch_color(self, hatch_color):
- """Set the hatch color."""
- self._hatch_color = hatch_color
- def get_hatch_linewidth(self):
- """Get the hatch linewidth."""
- return self._hatch_linewidth
- def get_sketch_params(self):
- """
- Return the sketch parameters for the artist.
- Returns
- -------
- tuple or `None`
- A 3-tuple with the following elements:
- * ``scale``: The amplitude of the wiggle perpendicular to the
- source line.
- * ``length``: The length of the wiggle along the line.
- * ``randomness``: The scale factor by which the length is
- shrunken or expanded.
- May return `None` if no sketch parameters were set.
- """
- return self._sketch
- def set_sketch_params(self, scale=None, length=None, randomness=None):
- """
- Set the sketch parameters.
- Parameters
- ----------
- scale : float, optional
- The amplitude of the wiggle perpendicular to the source line, in
- pixels. If scale is `None`, or not provided, no sketch filter will
- be provided.
- length : float, default: 128
- The length of the wiggle along the line, in pixels.
- randomness : float, default: 16
- The scale factor by which the length is shrunken or expanded.
- """
- self._sketch = (
- None if scale is None
- else (scale, length or 128., randomness or 16.))
- class TimerBase:
- """
- A base class for providing timer events, useful for things animations.
- Backends need to implement a few specific methods in order to use their
- own timing mechanisms so that the timer events are integrated into their
- event loops.
- Subclasses must override the following methods:
- - ``_timer_start``: Backend-specific code for starting the timer.
- - ``_timer_stop``: Backend-specific code for stopping the timer.
- Subclasses may additionally override the following methods:
- - ``_timer_set_single_shot``: Code for setting the timer to single shot
- operating mode, if supported by the timer object. If not, the `Timer`
- class itself will store the flag and the ``_on_timer`` method should be
- overridden to support such behavior.
- - ``_timer_set_interval``: Code for setting the interval on the timer, if
- there is a method for doing so on the timer object.
- - ``_on_timer``: The internal function that any timer object should call,
- which will handle the task of running all callbacks that have been set.
- """
- def __init__(self, interval=None, callbacks=None):
- """
- Parameters
- ----------
- interval : int, default: 1000ms
- The time between timer events in milliseconds. Will be stored as
- ``timer.interval``.
- callbacks : List[Tuple[callable, Tuple, Dict]]
- List of (func, args, kwargs) tuples that will be called upon
- timer events. This list is accessible as ``timer.callbacks`` and
- can be manipulated directly, or the functions `add_callback` and
- `remove_callback` can be used.
- """
- self.callbacks = [] if callbacks is None else callbacks.copy()
- # Set .interval and not ._interval to go through the property setter.
- self.interval = 1000 if interval is None else interval
- self.single_shot = False
- def __del__(self):
- """Need to stop timer and possibly disconnect timer."""
- self._timer_stop()
- def start(self, interval=None):
- """
- Start the timer object.
- Parameters
- ----------
- interval : int, optional
- Timer interval in milliseconds; overrides a previously set interval
- if provided.
- """
- if interval is not None:
- self.interval = interval
- self._timer_start()
- def stop(self):
- """Stop the timer."""
- self._timer_stop()
- def _timer_start(self):
- pass
- def _timer_stop(self):
- pass
- @property
- def interval(self):
- """The time between timer events, in milliseconds."""
- return self._interval
- @interval.setter
- def interval(self, interval):
- # Force to int since none of the backends actually support fractional
- # milliseconds, and some error or give warnings.
- interval = int(interval)
- self._interval = interval
- self._timer_set_interval()
- @property
- def single_shot(self):
- """Whether this timer should stop after a single run."""
- return self._single
- @single_shot.setter
- def single_shot(self, ss):
- self._single = ss
- self._timer_set_single_shot()
- def add_callback(self, func, *args, **kwargs):
- """
- Register *func* to be called by timer when the event fires. Any
- additional arguments provided will be passed to *func*.
- This function returns *func*, which makes it possible to use it as a
- decorator.
- """
- self.callbacks.append((func, args, kwargs))
- return func
- def remove_callback(self, func, *args, **kwargs):
- """
- Remove *func* from list of callbacks.
- *args* and *kwargs* are optional and used to distinguish between copies
- of the same function registered to be called with different arguments.
- This behavior is deprecated. In the future, ``*args, **kwargs`` won't
- be considered anymore; to keep a specific callback removable by itself,
- pass it to `add_callback` as a `functools.partial` object.
- """
- if args or kwargs:
- cbook.warn_deprecated(
- "3.1", message="In a future version, Timer.remove_callback "
- "will not take *args, **kwargs anymore, but remove all "
- "callbacks where the callable matches; to keep a specific "
- "callback removable by itself, pass it to add_callback as a "
- "functools.partial object.")
- self.callbacks.remove((func, args, kwargs))
- else:
- funcs = [c[0] for c in self.callbacks]
- if func in funcs:
- self.callbacks.pop(funcs.index(func))
- def _timer_set_interval(self):
- """Used to set interval on underlying timer object."""
- def _timer_set_single_shot(self):
- """Used to set single shot on underlying timer object."""
- def _on_timer(self):
- """
- Runs all function that have been registered as callbacks. Functions
- can return False (or 0) if they should not be called any more. If there
- are no callbacks, the timer is automatically stopped.
- """
- for func, args, kwargs in self.callbacks:
- ret = func(*args, **kwargs)
- # docstring above explains why we use `if ret == 0` here,
- # instead of `if not ret`.
- # This will also catch `ret == False` as `False == 0`
- # but does not annoy the linters
- # https://docs.python.org/3/library/stdtypes.html#boolean-values
- if ret == 0:
- self.callbacks.remove((func, args, kwargs))
- if len(self.callbacks) == 0:
- self.stop()
- class Event:
- """
- A Matplotlib event. Attach additional attributes as defined in
- :meth:`FigureCanvasBase.mpl_connect`. The following attributes
- are defined and shown with their default values
- Attributes
- ----------
- name : str
- The event name.
- canvas : `FigureCanvasBase`
- The backend-specific canvas instance generating the event.
- guiEvent
- The GUI event that triggered the Matplotlib event.
- """
- def __init__(self, name, canvas, guiEvent=None):
- self.name = name
- self.canvas = canvas
- self.guiEvent = guiEvent
- class DrawEvent(Event):
- """
- An event triggered by a draw operation on the canvas
- In most backends callbacks subscribed to this callback will be
- fired after the rendering is complete but before the screen is
- updated. Any extra artists drawn to the canvas's renderer will
- be reflected without an explicit call to ``blit``.
- .. warning::
- Calling ``canvas.draw`` and ``canvas.blit`` in these callbacks may
- not be safe with all backends and may cause infinite recursion.
- In addition to the `Event` attributes, the following event
- attributes are defined:
- Attributes
- ----------
- renderer : `RendererBase`
- The renderer for the draw event.
- """
- def __init__(self, name, canvas, renderer):
- Event.__init__(self, name, canvas)
- self.renderer = renderer
- class ResizeEvent(Event):
- """
- An event triggered by a canvas resize
- In addition to the `Event` attributes, the following event
- attributes are defined:
- Attributes
- ----------
- width : int
- Width of the canvas in pixels.
- height : int
- Height of the canvas in pixels.
- """
- def __init__(self, name, canvas):
- Event.__init__(self, name, canvas)
- self.width, self.height = canvas.get_width_height()
- class CloseEvent(Event):
- """An event triggered by a figure being closed."""
- class LocationEvent(Event):
- """
- An event that has a screen location.
- The following additional attributes are defined and shown with
- their default values.
- In addition to the `Event` attributes, the following
- event attributes are defined:
- Attributes
- ----------
- x : int
- x position - pixels from left of canvas.
- y : int
- y position - pixels from bottom of canvas.
- inaxes : `~.axes.Axes` or None
- The `~.axes.Axes` instance over which the mouse is, if any.
- xdata : float or None
- x data coordinate of the mouse.
- ydata : float or None
- y data coordinate of the mouse.
- """
- lastevent = None # the last event that was triggered before this one
- def __init__(self, name, canvas, x, y, guiEvent=None):
- """
- (*x*, *y*) in figure coords ((0, 0) = bottom left).
- """
- Event.__init__(self, name, canvas, guiEvent=guiEvent)
- # x position - pixels from left of canvas
- self.x = int(x) if x is not None else x
- # y position - pixels from right of canvas
- self.y = int(y) if y is not None else y
- self.inaxes = None # the Axes instance if mouse us over axes
- self.xdata = None # x coord of mouse in data coords
- self.ydata = None # y coord of mouse in data coords
- if x is None or y is None:
- # cannot check if event was in axes if no (x, y) info
- self._update_enter_leave()
- return
- if self.canvas.mouse_grabber is None:
- self.inaxes = self.canvas.inaxes((x, y))
- else:
- self.inaxes = self.canvas.mouse_grabber
- if self.inaxes is not None:
- try:
- trans = self.inaxes.transData.inverted()
- xdata, ydata = trans.transform((x, y))
- except ValueError:
- pass
- else:
- self.xdata = xdata
- self.ydata = ydata
- self._update_enter_leave()
- def _update_enter_leave(self):
- """Process the figure/axes enter leave events."""
- if LocationEvent.lastevent is not None:
- last = LocationEvent.lastevent
- if last.inaxes != self.inaxes:
- # process axes enter/leave events
- try:
- if last.inaxes is not None:
- last.canvas.callbacks.process('axes_leave_event', last)
- except Exception:
- pass
- # See ticket 2901582.
- # I think this is a valid exception to the rule
- # against catching all exceptions; if anything goes
- # wrong, we simply want to move on and process the
- # current event.
- if self.inaxes is not None:
- self.canvas.callbacks.process('axes_enter_event', self)
- else:
- # process a figure enter event
- if self.inaxes is not None:
- self.canvas.callbacks.process('axes_enter_event', self)
- LocationEvent.lastevent = self
- class MouseButton(IntEnum):
- LEFT = 1
- MIDDLE = 2
- RIGHT = 3
- BACK = 8
- FORWARD = 9
- class MouseEvent(LocationEvent):
- """
- A mouse event ('button_press_event',
- 'button_release_event',
- 'scroll_event',
- 'motion_notify_event').
- In addition to the `Event` and `LocationEvent`
- attributes, the following attributes are defined:
- Attributes
- ----------
- button : None or `MouseButton` or {'up', 'down'}
- The button pressed. 'up' and 'down' are used for scroll events.
- Note that in the nbagg backend, both the middle and right clicks
- return RIGHT since right clicking will bring up the context menu in
- some browsers.
- Note that LEFT and RIGHT actually refer to the "primary" and
- "secondary" buttons, i.e. if the user inverts their left and right
- buttons ("left-handed setting") then the LEFT button will be the one
- physically on the right.
- key : None or str
- The key pressed when the mouse event triggered, e.g. 'shift'.
- See `KeyEvent`.
- .. warning::
- This key is currently obtained from the last 'key_press_event' or
- 'key_release_event' that occurred within the canvas. Thus, if the
- last change of keyboard state occurred while the canvas did not have
- focus, this attribute will be wrong.
- step : float
- The number of scroll steps (positive for 'up', negative for 'down').
- This applies only to 'scroll_event' and defaults to 0 otherwise.
- dblclick : bool
- Whether the event is a double-click. This applies only to
- 'button_press_event' and is False otherwise. In particular, it's
- not used in 'button_release_event'.
- Examples
- --------
- ::
- def on_press(event):
- print('you pressed', event.button, event.xdata, event.ydata)
- cid = fig.canvas.mpl_connect('button_press_event', on_press)
- """
- def __init__(self, name, canvas, x, y, button=None, key=None,
- step=0, dblclick=False, guiEvent=None):
- """
- (*x*, *y*) in figure coords ((0, 0) = bottom left)
- button pressed None, 1, 2, 3, 'up', 'down'
- """
- if button in MouseButton.__members__.values():
- button = MouseButton(button)
- self.button = button
- self.key = key
- self.step = step
- self.dblclick = dblclick
- # super-init is deferred to the end because it calls back on
- # 'axes_enter_event', which requires a fully initialized event.
- LocationEvent.__init__(self, name, canvas, x, y, guiEvent=guiEvent)
- def __str__(self):
- return (f"{self.name}: "
- f"xy=({self.x}, {self.y}) xydata=({self.xdata}, {self.ydata}) "
- f"button={self.button} dblclick={self.dblclick} "
- f"inaxes={self.inaxes}")
- class PickEvent(Event):
- """
- A pick event, fired when the user picks a location on the canvas
- sufficiently close to an artist.
- Attrs: all the `Event` attributes plus
- Attributes
- ----------
- mouseevent : `MouseEvent`
- The mouse event that generated the pick.
- artist : `matplotlib.artist.Artist`
- The picked artist.
- other
- Additional attributes may be present depending on the type of the
- picked object; e.g., a `~.Line2D` pick may define different extra
- attributes than a `~.PatchCollection` pick.
- Examples
- --------
- Bind a function ``on_pick()`` to pick events, that prints the coordinates
- of the picked data point::
- ax.plot(np.rand(100), 'o', picker=5) # 5 points tolerance
- def on_pick(event):
- line = event.artist
- xdata, ydata = line.get_data()
- ind = event.ind
- print('on pick line:', np.array([xdata[ind], ydata[ind]]).T)
- cid = fig.canvas.mpl_connect('pick_event', on_pick)
- """
- def __init__(self, name, canvas, mouseevent, artist,
- guiEvent=None, **kwargs):
- Event.__init__(self, name, canvas, guiEvent)
- self.mouseevent = mouseevent
- self.artist = artist
- self.__dict__.update(kwargs)
- class KeyEvent(LocationEvent):
- """
- A key event (key press, key release).
- Attach additional attributes as defined in
- :meth:`FigureCanvasBase.mpl_connect`.
- In addition to the `Event` and `LocationEvent`
- attributes, the following attributes are defined:
- Attributes
- ----------
- key : None or str
- the key(s) pressed. Could be **None**, a single case sensitive ascii
- character ("g", "G", "#", etc.), a special key
- ("control", "shift", "f1", "up", etc.) or a
- combination of the above (e.g., "ctrl+alt+g", "ctrl+alt+G").
- Notes
- -----
- Modifier keys will be prefixed to the pressed key and will be in the order
- "ctrl", "alt", "super". The exception to this rule is when the pressed key
- is itself a modifier key, therefore "ctrl+alt" and "alt+control" can both
- be valid key values.
- Examples
- --------
- ::
- def on_key(event):
- print('you pressed', event.key, event.xdata, event.ydata)
- cid = fig.canvas.mpl_connect('key_press_event', on_key)
- """
- def __init__(self, name, canvas, key, x=0, y=0, guiEvent=None):
- self.key = key
- # super-init deferred to the end: callback errors if called before
- LocationEvent.__init__(self, name, canvas, x, y, guiEvent=guiEvent)
- def _get_renderer(figure, print_method=None):
- """
- Get the renderer that would be used to save a `~.Figure`, and cache it on
- the figure.
- If you need a renderer without any active draw methods use
- renderer._draw_disabled to temporary patch them out at your call site.
- """
- # This is implemented by triggering a draw, then immediately jumping out of
- # Figure.draw() by raising an exception.
- class Done(Exception):
- pass
- def _draw(renderer): raise Done(renderer)
- with cbook._setattr_cm(figure, draw=_draw):
- orig_canvas = figure.canvas
- if print_method is None:
- fmt = figure.canvas.get_default_filetype()
- # Even for a canvas' default output type, a canvas switch may be
- # needed, e.g. for FigureCanvasBase.
- print_method = getattr(
- figure.canvas._get_output_canvas(None, fmt), f"print_{fmt}")
- try:
- print_method(io.BytesIO(), dpi=figure.dpi)
- except Done as exc:
- renderer, = figure._cachedRenderer, = exc.args
- return renderer
- else:
- raise RuntimeError(f"{print_method} did not call Figure.draw, so "
- f"no renderer is available")
- finally:
- figure.canvas = orig_canvas
- def _is_non_interactive_terminal_ipython(ip):
- """
- Return whether we are in a a terminal IPython, but non interactive.
- When in _terminal_ IPython, ip.parent will have and `interact` attribute,
- if this attribute is False we do not setup eventloop integration as the
- user will _not_ interact with IPython. In all other case (ZMQKernel, or is
- interactive), we do.
- """
- return (hasattr(ip, 'parent')
- and (ip.parent is not None)
- and getattr(ip.parent, 'interact', None) is False)
- def _check_savefig_extra_args(func=None, extra_kwargs=()):
- """
- Decorator for the final print_* methods that accept keyword arguments.
- If any unused keyword arguments are left, this decorator will warn about
- them, and as part of the warning, will attempt to specify the function that
- the user actually called, instead of the backend-specific method. If unable
- to determine which function the user called, it will specify `.savefig`.
- For compatibility across backends, this does not warn about keyword
- arguments added by `FigureCanvasBase.print_figure` for use in a subset of
- backends, because the user would not have added them directly.
- """
- if func is None:
- return functools.partial(_check_savefig_extra_args,
- extra_kwargs=extra_kwargs)
- old_sig = inspect.signature(func)
- @functools.wraps(func)
- def wrapper(*args, **kwargs):
- name = 'savefig' # Reasonable default guess.
- public_api = re.compile(r'^savefig|print_[A-Za-z0-9]+$')
- seen_print_figure = False
- for frame, line in traceback.walk_stack(None):
- if frame is None:
- # when called in embedded context may hit frame is None.
- break
- if re.match(r'\A(matplotlib|mpl_toolkits)(\Z|\.(?!tests\.))',
- # Work around sphinx-gallery not setting __name__.
- frame.f_globals.get('__name__', '')):
- if public_api.match(frame.f_code.co_name):
- name = frame.f_code.co_name
- if name == 'print_figure':
- seen_print_figure = True
- else:
- break
- accepted_kwargs = {*old_sig.parameters, *extra_kwargs}
- if seen_print_figure:
- for kw in ['dpi', 'facecolor', 'edgecolor', 'orientation',
- 'bbox_inches_restore']:
- # Ignore keyword arguments that are passed in by print_figure
- # for the use of other renderers.
- if kw not in accepted_kwargs:
- kwargs.pop(kw, None)
- for arg in list(kwargs):
- if arg in accepted_kwargs:
- continue
- cbook.warn_deprecated(
- '3.3', name=name,
- message='%(name)s() got unexpected keyword argument "'
- + arg + '" which is no longer supported as of '
- '%(since)s and will become an error '
- '%(removal)s')
- kwargs.pop(arg)
- return func(*args, **kwargs)
- return wrapper
- class FigureCanvasBase:
- """
- The canvas the figure renders into.
- Attributes
- ----------
- figure : `matplotlib.figure.Figure`
- A high-level figure instance.
- """
- # Set to one of {"qt5", "qt4", "gtk3", "wx", "tk", "macosx"} if an
- # interactive framework is required, or None otherwise.
- required_interactive_framework = None
- events = [
- 'resize_event',
- 'draw_event',
- 'key_press_event',
- 'key_release_event',
- 'button_press_event',
- 'button_release_event',
- 'scroll_event',
- 'motion_notify_event',
- 'pick_event',
- 'figure_enter_event',
- 'figure_leave_event',
- 'axes_enter_event',
- 'axes_leave_event',
- 'close_event'
- ]
- fixed_dpi = None
- filetypes = _default_filetypes
- @cbook._classproperty
- def supports_blit(cls):
- """If this Canvas sub-class supports blitting."""
- return (hasattr(cls, "copy_from_bbox")
- and hasattr(cls, "restore_region"))
- def __init__(self, figure):
- self._fix_ipython_backend2gui()
- self._is_idle_drawing = True
- self._is_saving = False
- figure.set_canvas(self)
- self.figure = figure
- self.manager = None
- # a dictionary from event name to a dictionary that maps cid->func
- self.callbacks = cbook.CallbackRegistry()
- self.widgetlock = widgets.LockDraw()
- self._button = None # the button pressed
- self._key = None # the key pressed
- self._lastx, self._lasty = None, None
- self.button_pick_id = self.mpl_connect('button_press_event', self.pick)
- self.scroll_pick_id = self.mpl_connect('scroll_event', self.pick)
- self.mouse_grabber = None # the axes currently grabbing mouse
- self.toolbar = None # NavigationToolbar2 will set me
- self._is_idle_drawing = False
- @classmethod
- @functools.lru_cache()
- def _fix_ipython_backend2gui(cls):
- # Fix hard-coded module -> toolkit mapping in IPython (used for
- # `ipython --auto`). This cannot be done at import time due to
- # ordering issues, so we do it when creating a canvas, and should only
- # be done once per class (hence the `lru_cache(1)`).
- if "IPython" not in sys.modules:
- return
- import IPython
- ip = IPython.get_ipython()
- if not ip:
- return
- from IPython.core import pylabtools as pt
- if (not hasattr(pt, "backend2gui")
- or not hasattr(ip, "enable_matplotlib")):
- # In case we ever move the patch to IPython and remove these APIs,
- # don't break on our side.
- return
- rif = getattr(cls, "required_interactive_framework", None)
- backend2gui_rif = {"qt5": "qt", "qt4": "qt", "gtk3": "gtk3",
- "wx": "wx", "macosx": "osx"}.get(rif)
- if backend2gui_rif:
- if _is_non_interactive_terminal_ipython(ip):
- ip.enable_gui(backend2gui_rif)
- @contextmanager
- def _idle_draw_cntx(self):
- self._is_idle_drawing = True
- try:
- yield
- finally:
- self._is_idle_drawing = False
- def is_saving(self):
- """
- Return whether the renderer is in the process of saving
- to a file, rather than rendering for an on-screen buffer.
- """
- return self._is_saving
- def pick(self, mouseevent):
- if not self.widgetlock.locked():
- self.figure.pick(mouseevent)
- def blit(self, bbox=None):
- """Blit the canvas in bbox (default entire canvas)."""
- def resize(self, w, h):
- """Set the canvas size in pixels."""
- def draw_event(self, renderer):
- """Pass a `DrawEvent` to all functions connected to ``draw_event``."""
- s = 'draw_event'
- event = DrawEvent(s, self, renderer)
- self.callbacks.process(s, event)
- def resize_event(self):
- """
- Pass a `ResizeEvent` to all functions connected to ``resize_event``.
- """
- s = 'resize_event'
- event = ResizeEvent(s, self)
- self.callbacks.process(s, event)
- self.draw_idle()
- def close_event(self, guiEvent=None):
- """
- Pass a `CloseEvent` to all functions connected to ``close_event``.
- """
- s = 'close_event'
- try:
- event = CloseEvent(s, self, guiEvent=guiEvent)
- self.callbacks.process(s, event)
- except (TypeError, AttributeError):
- pass
- # Suppress the TypeError when the python session is being killed.
- # It may be that a better solution would be a mechanism to
- # disconnect all callbacks upon shutdown.
- # AttributeError occurs on OSX with qt4agg upon exiting
- # with an open window; 'callbacks' attribute no longer exists.
- def key_press_event(self, key, guiEvent=None):
- """
- Pass a `KeyEvent` to all functions connected to ``key_press_event``.
- """
- self._key = key
- s = 'key_press_event'
- event = KeyEvent(
- s, self, key, self._lastx, self._lasty, guiEvent=guiEvent)
- self.callbacks.process(s, event)
- def key_release_event(self, key, guiEvent=None):
- """
- Pass a `KeyEvent` to all functions connected to ``key_release_event``.
- """
- s = 'key_release_event'
- event = KeyEvent(
- s, self, key, self._lastx, self._lasty, guiEvent=guiEvent)
- self.callbacks.process(s, event)
- self._key = None
- def pick_event(self, mouseevent, artist, **kwargs):
- """
- Callback processing for pick events.
- This method will be called by artists who are picked and will
- fire off `PickEvent` callbacks registered listeners.
- """
- s = 'pick_event'
- event = PickEvent(s, self, mouseevent, artist,
- guiEvent=mouseevent.guiEvent,
- **kwargs)
- self.callbacks.process(s, event)
- def scroll_event(self, x, y, step, guiEvent=None):
- """
- Callback processing for scroll events.
- Backend derived classes should call this function on any
- scroll wheel event. (*x*, *y*) are the canvas coords ((0, 0) is lower
- left). button and key are as defined in `MouseEvent`.
- This method will call all functions connected to the 'scroll_event'
- with a `MouseEvent` instance.
- """
- if step >= 0:
- self._button = 'up'
- else:
- self._button = 'down'
- s = 'scroll_event'
- mouseevent = MouseEvent(s, self, x, y, self._button, self._key,
- step=step, guiEvent=guiEvent)
- self.callbacks.process(s, mouseevent)
- def button_press_event(self, x, y, button, dblclick=False, guiEvent=None):
- """
- Callback processing for mouse button press events.
- Backend derived classes should call this function on any mouse
- button press. (*x*, *y*) are the canvas coords ((0, 0) is lower left).
- button and key are as defined in `MouseEvent`.
- This method will call all functions connected to the
- 'button_press_event' with a `MouseEvent` instance.
- """
- self._button = button
- s = 'button_press_event'
- mouseevent = MouseEvent(s, self, x, y, button, self._key,
- dblclick=dblclick, guiEvent=guiEvent)
- self.callbacks.process(s, mouseevent)
- def button_release_event(self, x, y, button, guiEvent=None):
- """
- Callback processing for mouse button release events.
- Backend derived classes should call this function on any mouse
- button release.
- This method will call all functions connected to the
- 'button_release_event' with a `MouseEvent` instance.
- Parameters
- ----------
- x : float
- The canvas coordinates where 0=left.
- y : float
- The canvas coordinates where 0=bottom.
- guiEvent
- The native UI event that generated the Matplotlib event.
- """
- s = 'button_release_event'
- event = MouseEvent(s, self, x, y, button, self._key, guiEvent=guiEvent)
- self.callbacks.process(s, event)
- self._button = None
- def motion_notify_event(self, x, y, guiEvent=None):
- """
- Callback processing for mouse movement events.
- Backend derived classes should call this function on any
- motion-notify-event.
- This method will call all functions connected to the
- 'motion_notify_event' with a `MouseEvent` instance.
- Parameters
- ----------
- x : float
- The canvas coordinates where 0=left.
- y : float
- The canvas coordinates where 0=bottom.
- guiEvent
- The native UI event that generated the Matplotlib event.
- """
- self._lastx, self._lasty = x, y
- s = 'motion_notify_event'
- event = MouseEvent(s, self, x, y, self._button, self._key,
- guiEvent=guiEvent)
- self.callbacks.process(s, event)
- def leave_notify_event(self, guiEvent=None):
- """
- Callback processing for the mouse cursor leaving the canvas.
- Backend derived classes should call this function when leaving
- canvas.
- Parameters
- ----------
- guiEvent
- The native UI event that generated the Matplotlib event.
- """
- self.callbacks.process('figure_leave_event', LocationEvent.lastevent)
- LocationEvent.lastevent = None
- self._lastx, self._lasty = None, None
- def enter_notify_event(self, guiEvent=None, xy=None):
- """
- Callback processing for the mouse cursor entering the canvas.
- Backend derived classes should call this function when entering
- canvas.
- Parameters
- ----------
- guiEvent
- The native UI event that generated the Matplotlib event.
- xy : (float, float)
- The coordinate location of the pointer when the canvas is entered.
- """
- if xy is not None:
- x, y = xy
- self._lastx, self._lasty = x, y
- else:
- x = None
- y = None
- cbook.warn_deprecated(
- '3.0', removal='3.5', name='enter_notify_event',
- message='Since %(since)s, %(name)s expects a location but '
- 'your backend did not pass one. This will become an error '
- '%(removal)s.')
- event = LocationEvent('figure_enter_event', self, x, y, guiEvent)
- self.callbacks.process('figure_enter_event', event)
- def inaxes(self, xy):
- """
- Return the topmost visible `~.axes.Axes` containing the point *xy*.
- Parameters
- ----------
- xy : (float, float)
- (x, y) pixel positions from left/bottom of the canvas.
- Returns
- -------
- `~matplotlib.axes.Axes` or None
- The topmost visible axes containing the point, or None if no axes.
- """
- axes_list = [a for a in self.figure.get_axes()
- if a.patch.contains_point(xy) and a.get_visible()]
- if axes_list:
- axes = cbook._topmost_artist(axes_list)
- else:
- axes = None
- return axes
- def grab_mouse(self, ax):
- """
- Set the child `~.axes.Axes` which is grabbing the mouse events.
- Usually called by the widgets themselves. It is an error to call this
- if the mouse is already grabbed by another axes.
- """
- if self.mouse_grabber not in (None, ax):
- raise RuntimeError("Another Axes already grabs mouse input")
- self.mouse_grabber = ax
- def release_mouse(self, ax):
- """
- Release the mouse grab held by the `~.axes.Axes` *ax*.
- Usually called by the widgets. It is ok to call this even if *ax*
- doesn't have the mouse grab currently.
- """
- if self.mouse_grabber is ax:
- self.mouse_grabber = None
- def draw(self, *args, **kwargs):
- """Render the `.Figure`."""
- def draw_idle(self, *args, **kwargs):
- """
- Request a widget redraw once control returns to the GUI event loop.
- Even if multiple calls to `draw_idle` occur before control returns
- to the GUI event loop, the figure will only be rendered once.
- Notes
- -----
- Backends may choose to override the method and implement their own
- strategy to prevent multiple renderings.
- """
- if not self._is_idle_drawing:
- with self._idle_draw_cntx():
- self.draw(*args, **kwargs)
- @cbook.deprecated("3.2")
- def draw_cursor(self, event):
- """
- Draw a cursor in the event.axes if inaxes is not None. Use
- native GUI drawing for efficiency if possible
- """
- def get_width_height(self):
- """
- Return the figure width and height in points or pixels
- (depending on the backend), truncated to integers.
- """
- return int(self.figure.bbox.width), int(self.figure.bbox.height)
- @classmethod
- def get_supported_filetypes(cls):
- """Return dict of savefig file formats supported by this backend."""
- return cls.filetypes
- @classmethod
- def get_supported_filetypes_grouped(cls):
- """
- Return a dict of savefig file formats supported by this backend,
- where the keys are a file type name, such as 'Joint Photographic
- Experts Group', and the values are a list of filename extensions used
- for that filetype, such as ['jpg', 'jpeg'].
- """
- groupings = {}
- for ext, name in cls.filetypes.items():
- groupings.setdefault(name, []).append(ext)
- groupings[name].sort()
- return groupings
- def _get_output_canvas(self, backend, fmt):
- """
- Set the canvas in preparation for saving the figure.
- Parameters
- ----------
- backend : str or None
- If not None, switch the figure canvas to the ``FigureCanvas`` class
- of the given backend.
- fmt : str
- If *backend* is None, then determine a suitable canvas class for
- saving to format *fmt* -- either the current canvas class, if it
- supports *fmt*, or whatever `get_registered_canvas_class` returns;
- switch the figure canvas to that canvas class.
- """
- if backend is not None:
- # Return a specific canvas class, if requested.
- canvas_class = (
- importlib.import_module(cbook._backend_module_name(backend))
- .FigureCanvas)
- if not hasattr(canvas_class, f"print_{fmt}"):
- raise ValueError(
- f"The {backend!r} backend does not support {fmt} output")
- elif hasattr(self, f"print_{fmt}"):
- # Return the current canvas if it supports the requested format.
- return self
- else:
- # Return a default canvas for the requested format, if it exists.
- canvas_class = get_registered_canvas_class(fmt)
- if canvas_class:
- return self.switch_backends(canvas_class)
- # Else report error for unsupported format.
- raise ValueError(
- "Format {!r} is not supported (supported formats: {})"
- .format(fmt, ", ".join(sorted(self.get_supported_filetypes()))))
- def print_figure(
- self, filename, dpi=None, facecolor=None, edgecolor=None,
- orientation='portrait', format=None, *,
- bbox_inches=None, pad_inches=None, bbox_extra_artists=None,
- backend=None, **kwargs):
- """
- Render the figure to hardcopy. Set the figure patch face and edge
- colors. This is useful because some of the GUIs have a gray figure
- face color background and you'll probably want to override this on
- hardcopy.
- Parameters
- ----------
- filename : str or path-like or file-like
- The file where the figure is saved.
- dpi : float, default: :rc:`savefig.dpi`
- The dots per inch to save the figure in.
- facecolor : color or 'auto', default: :rc:`savefig.facecolor`
- The facecolor of the figure. If 'auto', use the current figure
- facecolor.
- edgecolor : color or 'auto', default: :rc:`savefig.edgecolor`
- The edgecolor of the figure. If 'auto', use the current figure
- edgecolor.
- orientation : {'landscape', 'portrait'}, default: 'portrait'
- Only currently applies to PostScript printing.
- format : str, optional
- Force a specific file format. If not given, the format is inferred
- from the *filename* extension, and if that fails from
- :rc:`savefig.format`.
- bbox_inches : 'tight' or `.Bbox`, default: :rc:`savefig.bbox`
- Bounding box in inches: only the given portion of the figure is
- saved. If 'tight', try to figure out the tight bbox of the figure.
- pad_inches : float, default: :rc:`savefig.pad_inches`
- Amount of padding around the figure when *bbox_inches* is 'tight'.
- bbox_extra_artists : list of `~matplotlib.artist.Artist`, optional
- A list of extra artists that will be considered when the
- tight bbox is calculated.
- backend : str, optional
- Use a non-default backend to render the file, e.g. to render a
- png file with the "cairo" backend rather than the default "agg",
- or a pdf file with the "pgf" backend rather than the default
- "pdf". Note that the default backend is normally sufficient. See
- :ref:`the-builtin-backends` for a list of valid backends for each
- file format. Custom backends can be referenced as "module://...".
- """
- if format is None:
- # get format from filename, or from backend's default filetype
- if isinstance(filename, os.PathLike):
- filename = os.fspath(filename)
- if isinstance(filename, str):
- format = os.path.splitext(filename)[1][1:]
- if format is None or format == '':
- format = self.get_default_filetype()
- if isinstance(filename, str):
- filename = filename.rstrip('.') + '.' + format
- format = format.lower()
- # get canvas object and print method for format
- canvas = self._get_output_canvas(backend, format)
- print_method = getattr(canvas, 'print_%s' % format)
- if dpi is None:
- dpi = rcParams['savefig.dpi']
- if dpi == 'figure':
- dpi = getattr(self.figure, '_original_dpi', self.figure.dpi)
- # Remove the figure manager, if any, to avoid resizing the GUI widget.
- # Some code (e.g. Figure.show) differentiates between having *no*
- # manager and a *None* manager, which should be fixed at some point,
- # but this should be fine.
- with cbook._setattr_cm(self, manager=None), \
- cbook._setattr_cm(self.figure, dpi=dpi), \
- cbook._setattr_cm(canvas, _is_saving=True):
- origfacecolor = self.figure.get_facecolor()
- origedgecolor = self.figure.get_edgecolor()
- if facecolor is None:
- facecolor = rcParams['savefig.facecolor']
- if cbook._str_equal(facecolor, 'auto'):
- facecolor = origfacecolor
- if edgecolor is None:
- edgecolor = rcParams['savefig.edgecolor']
- if cbook._str_equal(edgecolor, 'auto'):
- edgecolor = origedgecolor
- self.figure.set_facecolor(facecolor)
- self.figure.set_edgecolor(edgecolor)
- if bbox_inches is None:
- bbox_inches = rcParams['savefig.bbox']
- if bbox_inches:
- if bbox_inches == "tight":
- renderer = _get_renderer(
- self.figure,
- functools.partial(
- print_method, orientation=orientation)
- )
- ctx = (renderer._draw_disabled()
- if hasattr(renderer, '_draw_disabled')
- else suppress())
- with ctx:
- self.figure.draw(renderer)
- bbox_inches = self.figure.get_tightbbox(
- renderer, bbox_extra_artists=bbox_extra_artists)
- if pad_inches is None:
- pad_inches = rcParams['savefig.pad_inches']
- bbox_inches = bbox_inches.padded(pad_inches)
- # call adjust_bbox to save only the given area
- restore_bbox = tight_bbox.adjust_bbox(self.figure, bbox_inches,
- canvas.fixed_dpi)
- _bbox_inches_restore = (bbox_inches, restore_bbox)
- else:
- _bbox_inches_restore = None
- try:
- result = print_method(
- filename,
- dpi=dpi,
- facecolor=facecolor,
- edgecolor=edgecolor,
- orientation=orientation,
- bbox_inches_restore=_bbox_inches_restore,
- **kwargs)
- finally:
- if bbox_inches and restore_bbox:
- restore_bbox()
- self.figure.set_facecolor(origfacecolor)
- self.figure.set_edgecolor(origedgecolor)
- self.figure.set_canvas(self)
- return result
- @classmethod
- def get_default_filetype(cls):
- """
- Return the default savefig file format as specified in
- :rc:`savefig.format`.
- The returned string does not include a period. This method is
- overridden in backends that only support a single file type.
- """
- return rcParams['savefig.format']
- def get_window_title(self):
- """
- Return the title text of the window containing the figure, or None
- if there is no window (e.g., a PS backend).
- """
- if self.manager is not None:
- return self.manager.get_window_title()
- def set_window_title(self, title):
- """
- Set the title text of the window containing the figure. Note that
- this has no effect if there is no window (e.g., a PS backend).
- """
- if self.manager is not None:
- self.manager.set_window_title(title)
- def get_default_filename(self):
- """
- Return a string, which includes extension, suitable for use as
- a default filename.
- """
- default_basename = self.get_window_title() or 'image'
- default_basename = default_basename.replace(' ', '_')
- default_filetype = self.get_default_filetype()
- default_filename = default_basename + '.' + default_filetype
- return default_filename
- def switch_backends(self, FigureCanvasClass):
- """
- Instantiate an instance of FigureCanvasClass
- This is used for backend switching, e.g., to instantiate a
- FigureCanvasPS from a FigureCanvasGTK. Note, deep copying is
- not done, so any changes to one of the instances (e.g., setting
- figure size or line props), will be reflected in the other
- """
- newCanvas = FigureCanvasClass(self.figure)
- newCanvas._is_saving = self._is_saving
- return newCanvas
- def mpl_connect(self, s, func):
- """
- Bind function *func* to event *s*.
- Parameters
- ----------
- s : str
- One of the following events ids:
- - 'button_press_event'
- - 'button_release_event'
- - 'draw_event'
- - 'key_press_event'
- - 'key_release_event'
- - 'motion_notify_event'
- - 'pick_event'
- - 'resize_event'
- - 'scroll_event'
- - 'figure_enter_event',
- - 'figure_leave_event',
- - 'axes_enter_event',
- - 'axes_leave_event'
- - 'close_event'.
- func : callable
- The callback function to be executed, which must have the
- signature::
- def func(event: Event) -> Any
- For the location events (button and key press/release), if the
- mouse is over the axes, the ``inaxes`` attribute of the event will
- be set to the `~matplotlib.axes.Axes` the event occurs is over, and
- additionally, the variables ``xdata`` and ``ydata`` attributes will
- be set to the mouse location in data coordinates. See `.KeyEvent`
- and `.MouseEvent` for more info.
- Returns
- -------
- cid
- A connection id that can be used with
- `.FigureCanvasBase.mpl_disconnect`.
- Examples
- --------
- ::
- def on_press(event):
- print('you pressed', event.button, event.xdata, event.ydata)
- cid = canvas.mpl_connect('button_press_event', on_press)
- """
- return self.callbacks.connect(s, func)
- def mpl_disconnect(self, cid):
- """
- Disconnect the callback with id *cid*.
- Examples
- --------
- ::
- cid = canvas.mpl_connect('button_press_event', on_press)
- # ... later
- canvas.mpl_disconnect(cid)
- """
- return self.callbacks.disconnect(cid)
- # Internal subclasses can override _timer_cls instead of new_timer, though
- # this is not a public API for third-party subclasses.
- _timer_cls = TimerBase
- def new_timer(self, interval=None, callbacks=None):
- """
- Create a new backend-specific subclass of `.Timer`.
- This is useful for getting periodic events through the backend's native
- event loop. Implemented only for backends with GUIs.
- Parameters
- ----------
- interval : int
- Timer interval in milliseconds.
- callbacks : List[Tuple[callable, Tuple, Dict]]
- Sequence of (func, args, kwargs) where ``func(*args, **kwargs)``
- will be executed by the timer every *interval*.
- Callbacks which return ``False`` or ``0`` will be removed from the
- timer.
- Examples
- --------
- >>> timer = fig.canvas.new_timer(callbacks=[(f1, (1,), {'a': 3})])
- """
- return self._timer_cls(interval=interval, callbacks=callbacks)
- def flush_events(self):
- """
- Flush the GUI events for the figure.
- Interactive backends need to reimplement this method.
- """
- def start_event_loop(self, timeout=0):
- """
- Start a blocking event loop.
- Such an event loop is used by interactive functions, such as
- `~.Figure.ginput` and `~.Figure.waitforbuttonpress`, to wait for
- events.
- The event loop blocks until a callback function triggers
- `stop_event_loop`, or *timeout* is reached.
- If *timeout* is 0 or negative, never timeout.
- Only interactive backends need to reimplement this method and it relies
- on `flush_events` being properly implemented.
- Interactive backends should implement this in a more native way.
- """
- if timeout <= 0:
- timeout = np.inf
- timestep = 0.01
- counter = 0
- self._looping = True
- while self._looping and counter * timestep < timeout:
- self.flush_events()
- time.sleep(timestep)
- counter += 1
- def stop_event_loop(self):
- """
- Stop the current blocking event loop.
- Interactive backends need to reimplement this to match
- `start_event_loop`
- """
- self._looping = False
- def key_press_handler(event, canvas=None, toolbar=None):
- """
- Implement the default Matplotlib key bindings for the canvas and toolbar
- described at :ref:`key-event-handling`.
- Parameters
- ----------
- event : `KeyEvent`
- A key press/release event.
- canvas : `FigureCanvasBase`, default: ``event.canvas``
- The backend-specific canvas instance. This parameter is kept for
- back-compatibility, but, if set, should always be equal to
- ``event.canvas``.
- toolbar : `NavigationToolbar2`, default: ``event.canvas.toolbar``
- The navigation cursor toolbar. This parameter is kept for
- back-compatibility, but, if set, should always be equal to
- ``event.canvas.toolbar``.
- """
- # these bindings happen whether you are over an axes or not
- if event.key is None:
- return
- if canvas is None:
- canvas = event.canvas
- if toolbar is None:
- toolbar = canvas.toolbar
- # Load key-mappings from rcParams.
- fullscreen_keys = rcParams['keymap.fullscreen']
- home_keys = rcParams['keymap.home']
- back_keys = rcParams['keymap.back']
- forward_keys = rcParams['keymap.forward']
- pan_keys = rcParams['keymap.pan']
- zoom_keys = rcParams['keymap.zoom']
- save_keys = rcParams['keymap.save']
- quit_keys = rcParams['keymap.quit']
- quit_all_keys = rcParams['keymap.quit_all']
- grid_keys = rcParams['keymap.grid']
- grid_minor_keys = rcParams['keymap.grid_minor']
- toggle_yscale_keys = rcParams['keymap.yscale']
- toggle_xscale_keys = rcParams['keymap.xscale']
- all_keys = dict.__getitem__(rcParams, 'keymap.all_axes')
- # toggle fullscreen mode ('f', 'ctrl + f')
- if event.key in fullscreen_keys:
- try:
- canvas.manager.full_screen_toggle()
- except AttributeError:
- pass
- # quit the figure (default key 'ctrl+w')
- if event.key in quit_keys:
- Gcf.destroy_fig(canvas.figure)
- if event.key in quit_all_keys:
- Gcf.destroy_all()
- if toolbar is not None:
- # home or reset mnemonic (default key 'h', 'home' and 'r')
- if event.key in home_keys:
- toolbar.home()
- # forward / backward keys to enable left handed quick navigation
- # (default key for backward: 'left', 'backspace' and 'c')
- elif event.key in back_keys:
- toolbar.back()
- # (default key for forward: 'right' and 'v')
- elif event.key in forward_keys:
- toolbar.forward()
- # pan mnemonic (default key 'p')
- elif event.key in pan_keys:
- toolbar.pan()
- toolbar._update_cursor(event)
- # zoom mnemonic (default key 'o')
- elif event.key in zoom_keys:
- toolbar.zoom()
- toolbar._update_cursor(event)
- # saving current figure (default key 's')
- elif event.key in save_keys:
- toolbar.save_figure()
- if event.inaxes is None:
- return
- # these bindings require the mouse to be over an axes to trigger
- def _get_uniform_gridstate(ticks):
- # Return True/False if all grid lines are on or off, None if they are
- # not all in the same state.
- if all(tick.gridline.get_visible() for tick in ticks):
- return True
- elif not any(tick.gridline.get_visible() for tick in ticks):
- return False
- else:
- return None
- ax = event.inaxes
- # toggle major grids in current axes (default key 'g')
- # Both here and below (for 'G'), we do nothing if *any* grid (major or
- # minor, x or y) is not in a uniform state, to avoid messing up user
- # customization.
- if (event.key in grid_keys
- # Exclude minor grids not in a uniform state.
- and None not in [_get_uniform_gridstate(ax.xaxis.minorTicks),
- _get_uniform_gridstate(ax.yaxis.minorTicks)]):
- x_state = _get_uniform_gridstate(ax.xaxis.majorTicks)
- y_state = _get_uniform_gridstate(ax.yaxis.majorTicks)
- cycle = [(False, False), (True, False), (True, True), (False, True)]
- try:
- x_state, y_state = (
- cycle[(cycle.index((x_state, y_state)) + 1) % len(cycle)])
- except ValueError:
- # Exclude major grids not in a uniform state.
- pass
- else:
- # If turning major grids off, also turn minor grids off.
- ax.grid(x_state, which="major" if x_state else "both", axis="x")
- ax.grid(y_state, which="major" if y_state else "both", axis="y")
- canvas.draw_idle()
- # toggle major and minor grids in current axes (default key 'G')
- if (event.key in grid_minor_keys
- # Exclude major grids not in a uniform state.
- and None not in [_get_uniform_gridstate(ax.xaxis.majorTicks),
- _get_uniform_gridstate(ax.yaxis.majorTicks)]):
- x_state = _get_uniform_gridstate(ax.xaxis.minorTicks)
- y_state = _get_uniform_gridstate(ax.yaxis.minorTicks)
- cycle = [(False, False), (True, False), (True, True), (False, True)]
- try:
- x_state, y_state = (
- cycle[(cycle.index((x_state, y_state)) + 1) % len(cycle)])
- except ValueError:
- # Exclude minor grids not in a uniform state.
- pass
- else:
- ax.grid(x_state, which="both", axis="x")
- ax.grid(y_state, which="both", axis="y")
- canvas.draw_idle()
- # toggle scaling of y-axes between 'log and 'linear' (default key 'l')
- elif event.key in toggle_yscale_keys:
- scale = ax.get_yscale()
- if scale == 'log':
- ax.set_yscale('linear')
- ax.figure.canvas.draw_idle()
- elif scale == 'linear':
- try:
- ax.set_yscale('log')
- except ValueError as exc:
- _log.warning(str(exc))
- ax.set_yscale('linear')
- ax.figure.canvas.draw_idle()
- # toggle scaling of x-axes between 'log and 'linear' (default key 'k')
- elif event.key in toggle_xscale_keys:
- scalex = ax.get_xscale()
- if scalex == 'log':
- ax.set_xscale('linear')
- ax.figure.canvas.draw_idle()
- elif scalex == 'linear':
- try:
- ax.set_xscale('log')
- except ValueError as exc:
- _log.warning(str(exc))
- ax.set_xscale('linear')
- ax.figure.canvas.draw_idle()
- # enable nagivation for all axes that contain the event (default key 'a')
- elif event.key in all_keys:
- for a in canvas.figure.get_axes():
- if (event.x is not None and event.y is not None
- and a.in_axes(event)): # FIXME: Why only these?
- cbook.warn_deprecated(
- "3.3", message="Toggling axes navigation from the "
- "keyboard is deprecated since %(since)s and will be "
- "removed %(removal)s.")
- a.set_navigate(True)
- # enable navigation only for axes with this index (if such an axes exist,
- # otherwise do nothing)
- elif event.key.isdigit() and event.key != '0':
- n = int(event.key) - 1
- if n < len(canvas.figure.get_axes()):
- for i, a in enumerate(canvas.figure.get_axes()):
- if (event.x is not None and event.y is not None
- and a.in_axes(event)): # FIXME: Why only these?
- cbook.warn_deprecated(
- "3.3", message="Toggling axes navigation from the "
- "keyboard is deprecated since %(since)s and will be "
- "removed %(removal)s.")
- a.set_navigate(i == n)
- def button_press_handler(event, canvas=None, toolbar=None):
- """
- The default Matplotlib button actions for extra mouse buttons.
- Parameters are as for `key_press_handler`, except that *event* is a
- `MouseEvent`.
- """
- if canvas is None:
- canvas = event.canvas
- if toolbar is None:
- toolbar = canvas.toolbar
- if toolbar is not None:
- button_name = str(MouseButton(event.button))
- if button_name in rcParams['keymap.back']:
- toolbar.back()
- elif button_name in rcParams['keymap.forward']:
- toolbar.forward()
- class NonGuiException(Exception):
- """Raised when trying show a figure in a non-GUI backend."""
- pass
- class FigureManagerBase:
- """
- A backend-independent abstraction of a figure container and controller.
- The figure manager is used by pyplot to interact with the window in a
- backend-independent way. It's an adapter for the real (GUI) framework that
- represents the visual figure on screen.
- GUI backends define from this class to translate common operations such
- as *show* or *resize* to the GUI-specific code. Non-GUI backends do not
- support these operations an can just use the base class.
- This following basic operations are accessible:
- **Window operations**
- - `~.FigureManagerBase.show`
- - `~.FigureManagerBase.destroy`
- - `~.FigureManagerBase.full_screen_toggle`
- - `~.FigureManagerBase.resize`
- - `~.FigureManagerBase.get_window_title`
- - `~.FigureManagerBase.set_window_title`
- **Key and mouse button press handling**
- The figure manager sets up default key and mouse button press handling by
- hooking up the `.key_press_handler` to the matplotlib event system. This
- ensures the same shortcuts and mouse actions across backends.
- **Other operations**
- Subclasses will have additional attributes and functions to access
- additional functionality. This is of course backend-specific. For example,
- most GUI backends have ``window`` and ``toolbar`` attributes that give
- access to the native GUI widgets of the respective framework.
- Attributes
- ----------
- canvas : `FigureCanvasBase`
- The backend-specific canvas instance.
- num : int or str
- The figure number.
- key_press_handler_id : int
- The default key handler cid, when using the toolmanager.
- To disable the default key press handling use::
- figure.canvas.mpl_disconnect(
- figure.canvas.manager.key_press_handler_id)
- button_press_handler_id : int
- The default mouse button handler cid, when using the toolmanager.
- To disable the default button press handling use::
- figure.canvas.mpl_disconnect(
- figure.canvas.manager.button_press_handler_id)
- """
- def __init__(self, canvas, num):
- self.canvas = canvas
- canvas.manager = self # store a pointer to parent
- self.num = num
- self.key_press_handler_id = None
- self.button_press_handler_id = None
- if rcParams['toolbar'] != 'toolmanager':
- self.key_press_handler_id = self.canvas.mpl_connect(
- 'key_press_event',
- self.key_press)
- self.button_press_handler_id = self.canvas.mpl_connect(
- 'button_press_event',
- self.button_press)
- self.toolmanager = (ToolManager(canvas.figure)
- if mpl.rcParams['toolbar'] == 'toolmanager'
- else None)
- self.toolbar = None
- @self.canvas.figure.add_axobserver
- def notify_axes_change(fig):
- # Called whenever the current axes is changed.
- if self.toolmanager is None and self.toolbar is not None:
- self.toolbar.update()
- @cbook.deprecated("3.3")
- @property
- def statusbar(self):
- return None
- def show(self):
- """
- For GUI backends, show the figure window and redraw.
- For non-GUI backends, raise an exception, unless running headless (i.e.
- on Linux with an unset DISPLAY); this exception is converted to a
- warning in `.Figure.show`.
- """
- # This should be overridden in GUI backends.
- if cbook._get_running_interactive_framework() != "headless":
- raise NonGuiException(
- f"Matplotlib is currently using {get_backend()}, which is "
- f"a non-GUI backend, so cannot show the figure.")
- def destroy(self):
- pass
- def full_screen_toggle(self):
- pass
- def resize(self, w, h):
- """For GUI backends, resize the window (in pixels)."""
- def key_press(self, event):
- """
- Implement the default Matplotlib key bindings defined at
- :ref:`key-event-handling`.
- """
- if rcParams['toolbar'] != 'toolmanager':
- key_press_handler(event)
- def button_press(self, event):
- """The default Matplotlib button actions for extra mouse buttons."""
- if rcParams['toolbar'] != 'toolmanager':
- button_press_handler(event)
- def get_window_title(self):
- """
- Return the title text of the window containing the figure, or None
- if there is no window (e.g., a PS backend).
- """
- return 'image'
- def set_window_title(self, title):
- """
- Set the title text of the window containing the figure.
- This has no effect for non-GUI (e.g., PS) backends.
- """
- cursors = tools.cursors
- class _Mode(str, Enum):
- NONE = ""
- PAN = "pan/zoom"
- ZOOM = "zoom rect"
- def __str__(self):
- return self.value
- @property
- def _navigate_mode(self):
- return self.name if self is not _Mode.NONE else None
- class NavigationToolbar2:
- """
- Base class for the navigation cursor, version 2.
- Backends must implement a canvas that handles connections for
- 'button_press_event' and 'button_release_event'. See
- :meth:`FigureCanvasBase.mpl_connect` for more information.
- They must also define
- :meth:`save_figure`
- save the current figure
- :meth:`set_cursor`
- if you want the pointer icon to change
- :meth:`draw_rubberband` (optional)
- draw the zoom to rect "rubberband" rectangle
- :meth:`set_message` (optional)
- display message
- :meth:`set_history_buttons` (optional)
- you can change the history back / forward buttons to
- indicate disabled / enabled state.
- and override ``__init__`` to set up the toolbar -- without forgetting to
- call the base-class init. Typically, ``__init__`` needs to set up toolbar
- buttons connected to the `home`, `back`, `forward`, `pan`, `zoom`, and
- `save_figure` methods and using standard icons in the "images" subdirectory
- of the data path.
- That's it, we'll do the rest!
- """
- # list of toolitems to add to the toolbar, format is:
- # (
- # text, # the text of the button (often not visible to users)
- # tooltip_text, # the tooltip shown on hover (where possible)
- # image_file, # name of the image for the button (without the extension)
- # name_of_method, # name of the method in NavigationToolbar2 to call
- # )
- toolitems = (
- ('Home', 'Reset original view', 'home', 'home'),
- ('Back', 'Back to previous view', 'back', 'back'),
- ('Forward', 'Forward to next view', 'forward', 'forward'),
- (None, None, None, None),
- ('Pan',
- 'Left button pans, Right button zooms\n'
- 'x/y fixes axis, CTRL fixes aspect',
- 'move', 'pan'),
- ('Zoom', 'Zoom to rectangle\nx/y fixes axis, CTRL fixes aspect',
- 'zoom_to_rect', 'zoom'),
- ('Subplots', 'Configure subplots', 'subplots', 'configure_subplots'),
- (None, None, None, None),
- ('Save', 'Save the figure', 'filesave', 'save_figure'),
- )
- def __init__(self, canvas):
- self.canvas = canvas
- canvas.toolbar = self
- self._nav_stack = cbook.Stack()
- self._xypress = None # location and axis info at the time of the press
- # This cursor will be set after the initial draw.
- self._lastCursor = cursors.POINTER
- init = cbook._deprecate_method_override(
- __class__._init_toolbar, self, allow_empty=True, since="3.3",
- addendum="Please fully initialize the toolbar in your subclass' "
- "__init__; a fully empty _init_toolbar implementation may be kept "
- "for compatibility with earlier versions of Matplotlib.")
- if init:
- init()
- self._id_press = self.canvas.mpl_connect(
- 'button_press_event', self._zoom_pan_handler)
- self._id_release = self.canvas.mpl_connect(
- 'button_release_event', self._zoom_pan_handler)
- self._id_drag = self.canvas.mpl_connect(
- 'motion_notify_event', self.mouse_move)
- self._zoom_info = None
- self._button_pressed = None # determined by button pressed at start
- self.mode = _Mode.NONE # a mode string for the status bar
- self.set_history_buttons()
- def set_message(self, s):
- """Display a message on toolbar or in status bar."""
- def draw_rubberband(self, event, x0, y0, x1, y1):
- """
- Draw a rectangle rubberband to indicate zoom limits.
- Note that it is not guaranteed that ``x0 <= x1`` and ``y0 <= y1``.
- """
- def remove_rubberband(self):
- """Remove the rubberband."""
- def home(self, *args):
- """
- Restore the original view.
- For convenience of being directly connected as a GUI callback, which
- often get passed additional parameters, this method accepts arbitrary
- parameters, but does not use them.
- """
- self._nav_stack.home()
- self.set_history_buttons()
- self._update_view()
- def back(self, *args):
- """
- Move back up the view lim stack.
- For convenience of being directly connected as a GUI callback, which
- often get passed additional parameters, this method accepts arbitrary
- parameters, but does not use them.
- """
- self._nav_stack.back()
- self.set_history_buttons()
- self._update_view()
- def forward(self, *args):
- """
- Move forward in the view lim stack.
- For convenience of being directly connected as a GUI callback, which
- often get passed additional parameters, this method accepts arbitrary
- parameters, but does not use them.
- """
- self._nav_stack.forward()
- self.set_history_buttons()
- self._update_view()
- @cbook.deprecated("3.3", alternative="__init__")
- def _init_toolbar(self):
- """
- This is where you actually build the GUI widgets (called by
- __init__). The icons ``home.xpm``, ``back.xpm``, ``forward.xpm``,
- ``hand.xpm``, ``zoom_to_rect.xpm`` and ``filesave.xpm`` are standard
- across backends (there are ppm versions in CVS also).
- You just need to set the callbacks
- home : self.home
- back : self.back
- forward : self.forward
- hand : self.pan
- zoom_to_rect : self.zoom
- filesave : self.save_figure
- You only need to define the last one - the others are in the base
- class implementation.
- """
- raise NotImplementedError
- def _update_cursor(self, event):
- """
- Update the cursor after a mouse move event or a tool (de)activation.
- """
- if not event.inaxes or not self.mode:
- if self._lastCursor != cursors.POINTER:
- self.set_cursor(cursors.POINTER)
- self._lastCursor = cursors.POINTER
- else:
- if (self.mode == _Mode.ZOOM
- and self._lastCursor != cursors.SELECT_REGION):
- self.set_cursor(cursors.SELECT_REGION)
- self._lastCursor = cursors.SELECT_REGION
- elif (self.mode == _Mode.PAN
- and self._lastCursor != cursors.MOVE):
- self.set_cursor(cursors.MOVE)
- self._lastCursor = cursors.MOVE
- @contextmanager
- def _wait_cursor_for_draw_cm(self):
- """
- Set the cursor to a wait cursor when drawing the canvas.
- In order to avoid constantly changing the cursor when the canvas
- changes frequently, do nothing if this context was triggered during the
- last second. (Optimally we'd prefer only setting the wait cursor if
- the *current* draw takes too long, but the current draw blocks the GUI
- thread).
- """
- self._draw_time, last_draw_time = (
- time.time(), getattr(self, "_draw_time", -np.inf))
- if self._draw_time - last_draw_time > 1:
- try:
- self.set_cursor(cursors.WAIT)
- yield
- finally:
- self.set_cursor(self._lastCursor)
- else:
- yield
- def mouse_move(self, event):
- self._update_cursor(event)
- if event.inaxes and event.inaxes.get_navigate():
- try:
- s = event.inaxes.format_coord(event.xdata, event.ydata)
- except (ValueError, OverflowError):
- pass
- else:
- s = s.rstrip()
- artists = [a for a in event.inaxes._mouseover_set
- if a.contains(event)[0] and a.get_visible()]
- if artists:
- a = cbook._topmost_artist(artists)
- if a is not event.inaxes.patch:
- data = a.get_cursor_data(event)
- if data is not None:
- data_str = a.format_cursor_data(data).rstrip()
- if data_str:
- s = s + '\n' + data_str
- self.set_message(s)
- else:
- self.set_message(self.mode)
- def _zoom_pan_handler(self, event):
- if self.mode == _Mode.PAN:
- if event.name == "button_press_event":
- self.press_pan(event)
- elif event.name == "button_release_event":
- self.release_pan(event)
- if self.mode == _Mode.ZOOM:
- if event.name == "button_press_event":
- self.press_zoom(event)
- elif event.name == "button_release_event":
- self.release_zoom(event)
- @cbook.deprecated("3.3")
- def press(self, event):
- """Called whenever a mouse button is pressed."""
- @cbook.deprecated("3.3")
- def release(self, event):
- """Callback for mouse button release."""
- def pan(self, *args):
- """
- Toggle the pan/zoom tool.
- Pan with left button, zoom with right.
- """
- if self.mode == _Mode.PAN:
- self.mode = _Mode.NONE
- self.canvas.widgetlock.release(self)
- else:
- self.mode = _Mode.PAN
- self.canvas.widgetlock(self)
- for a in self.canvas.figure.get_axes():
- a.set_navigate_mode(self.mode._navigate_mode)
- self.set_message(self.mode)
- def press_pan(self, event):
- """Callback for mouse button press in pan/zoom mode."""
- if event.button in [1, 3]:
- self._button_pressed = event.button
- else:
- self._button_pressed = None
- return
- if self._nav_stack() is None:
- # set the home button to this view
- self.push_current()
- x, y = event.x, event.y
- self._xypress = []
- for i, a in enumerate(self.canvas.figure.get_axes()):
- if (x is not None and y is not None and a.in_axes(event) and
- a.get_navigate() and a.can_pan()):
- a.start_pan(x, y, event.button)
- self._xypress.append((a, i))
- self.canvas.mpl_disconnect(self._id_drag)
- self._id_drag = self.canvas.mpl_connect(
- 'motion_notify_event', self.drag_pan)
- press = cbook._deprecate_method_override(
- __class__.press, self, since="3.3", message="Calling an "
- "overridden press() at pan start is deprecated since %(since)s "
- "and will be removed %(removal)s; override press_pan() instead.")
- if press is not None:
- press(event)
- def drag_pan(self, event):
- """Callback for dragging in pan/zoom mode."""
- for a, ind in self._xypress:
- #safer to use the recorded button at the press than current button:
- #multiple button can get pressed during motion...
- a.drag_pan(self._button_pressed, event.key, event.x, event.y)
- self.canvas.draw_idle()
- def release_pan(self, event):
- """Callback for mouse button release in pan/zoom mode."""
- if self._button_pressed is None:
- return
- self.canvas.mpl_disconnect(self._id_drag)
- self._id_drag = self.canvas.mpl_connect(
- 'motion_notify_event', self.mouse_move)
- for a, ind in self._xypress:
- a.end_pan()
- if not self._xypress:
- return
- self._xypress = []
- self._button_pressed = None
- self.push_current()
- release = cbook._deprecate_method_override(
- __class__.press, self, since="3.3", message="Calling an "
- "overridden release() at pan stop is deprecated since %(since)s "
- "and will be removed %(removal)s; override release_pan() instead.")
- if release is not None:
- release(event)
- self._draw()
- def zoom(self, *args):
- """Toggle zoom to rect mode."""
- if self.mode == _Mode.ZOOM:
- self.mode = _Mode.NONE
- self.canvas.widgetlock.release(self)
- else:
- self.mode = _Mode.ZOOM
- self.canvas.widgetlock(self)
- for a in self.canvas.figure.get_axes():
- a.set_navigate_mode(self.mode._navigate_mode)
- self.set_message(self.mode)
- def press_zoom(self, event):
- """Callback for mouse button press in zoom to rect mode."""
- if event.button not in [1, 3]:
- return
- if event.x is None or event.y is None:
- return
- axes = [a for a in self.canvas.figure.get_axes()
- if a.in_axes(event) and a.get_navigate() and a.can_zoom()]
- if not axes:
- return
- if self._nav_stack() is None:
- self.push_current() # set the home button to this view
- id_zoom = self.canvas.mpl_connect(
- "motion_notify_event", self.drag_zoom)
- self._zoom_info = {
- "direction": "in" if event.button == 1 else "out",
- "start_xy": (event.x, event.y),
- "axes": axes,
- "cid": id_zoom,
- }
- press = cbook._deprecate_method_override(
- __class__.press, self, since="3.3", message="Calling an "
- "overridden press() at zoom start is deprecated since %(since)s "
- "and will be removed %(removal)s; override press_zoom() instead.")
- if press is not None:
- press(event)
- def drag_zoom(self, event):
- """Callback for dragging in zoom mode."""
- start_xy = self._zoom_info["start_xy"]
- ax = self._zoom_info["axes"][0]
- (x1, y1), (x2, y2) = np.clip(
- [start_xy, [event.x, event.y]], ax.bbox.min, ax.bbox.max)
- if event.key == "x":
- y1, y2 = ax.bbox.intervaly
- elif event.key == "y":
- x1, x2 = ax.bbox.intervalx
- self.draw_rubberband(event, x1, y1, x2, y2)
- def release_zoom(self, event):
- """Callback for mouse button release in zoom to rect mode."""
- if self._zoom_info is None:
- return
- # We don't check the event button here, so that zooms can be cancelled
- # by (pressing and) releasing another mouse button.
- self.canvas.mpl_disconnect(self._zoom_info["cid"])
- self.remove_rubberband()
- start_x, start_y = self._zoom_info["start_xy"]
- for i, ax in enumerate(self._zoom_info["axes"]):
- x, y = event.x, event.y
- # ignore singular clicks - 5 pixels is a threshold
- # allows the user to "cancel" a zoom action
- # by zooming by less than 5 pixels
- if ((abs(x - start_x) < 5 and event.key != "y") or
- (abs(y - start_y) < 5 and event.key != "x")):
- self._xypress = None
- release = cbook._deprecate_method_override(
- __class__.press, self, since="3.3", message="Calling an "
- "overridden release() at zoom stop is deprecated since "
- "%(since)s and will be removed %(removal)s; override "
- "release_zoom() instead.")
- if release is not None:
- release(event)
- self._draw()
- return
- # Detect whether this axes is twinned with an earlier axes in the
- # list of zoomed axes, to avoid double zooming.
- twinx = any(ax.get_shared_x_axes().joined(ax, prev)
- for prev in self._zoom_info["axes"][:i])
- twiny = any(ax.get_shared_y_axes().joined(ax, prev)
- for prev in self._zoom_info["axes"][:i])
- ax._set_view_from_bbox(
- (start_x, start_y, x, y), self._zoom_info["direction"],
- event.key, twinx, twiny)
- self._draw()
- self._zoom_info = None
- self.push_current()
- release = cbook._deprecate_method_override(
- __class__.release, self, since="3.3", message="Calling an "
- "overridden release() at zoom stop is deprecated since %(since)s "
- "and will be removed %(removal)s; override release_zoom() "
- "instead.")
- if release is not None:
- release(event)
- def push_current(self):
- """Push the current view limits and position onto the stack."""
- self._nav_stack.push(
- WeakKeyDictionary(
- {ax: (ax._get_view(),
- # Store both the original and modified positions.
- (ax.get_position(True).frozen(),
- ax.get_position().frozen()))
- for ax in self.canvas.figure.axes}))
- self.set_history_buttons()
- @cbook.deprecated("3.3", alternative="toolbar.canvas.draw_idle()")
- def draw(self):
- """Redraw the canvases, update the locators."""
- self._draw()
- # Can be removed once Locator.refresh() is removed, and replaced by an
- # inline call to self.canvas.draw_idle().
- def _draw(self):
- for a in self.canvas.figure.get_axes():
- xaxis = getattr(a, 'xaxis', None)
- yaxis = getattr(a, 'yaxis', None)
- locators = []
- if xaxis is not None:
- locators.append(xaxis.get_major_locator())
- locators.append(xaxis.get_minor_locator())
- if yaxis is not None:
- locators.append(yaxis.get_major_locator())
- locators.append(yaxis.get_minor_locator())
- for loc in locators:
- mpl.ticker._if_refresh_overridden_call_and_emit_deprec(loc)
- self.canvas.draw_idle()
- def _update_view(self):
- """
- Update the viewlim and position from the view and position stack for
- each axes.
- """
- nav_info = self._nav_stack()
- if nav_info is None:
- return
- # Retrieve all items at once to avoid any risk of GC deleting an Axes
- # while in the middle of the loop below.
- items = list(nav_info.items())
- for ax, (view, (pos_orig, pos_active)) in items:
- ax._set_view(view)
- # Restore both the original and modified positions
- ax._set_position(pos_orig, 'original')
- ax._set_position(pos_active, 'active')
- self.canvas.draw_idle()
- def save_figure(self, *args):
- """Save the current figure."""
- raise NotImplementedError
- def set_cursor(self, cursor):
- """
- Set the current cursor to one of the :class:`Cursors` enums values.
- If required by the backend, this method should trigger an update in
- the backend event loop after the cursor is set, as this method may be
- called e.g. before a long-running task during which the GUI is not
- updated.
- """
- def update(self):
- """Reset the axes stack."""
- self._nav_stack.clear()
- self.set_history_buttons()
- def set_history_buttons(self):
- """Enable or disable the back/forward button."""
- class ToolContainerBase:
- """
- Base class for all tool containers, e.g. toolbars.
- Attributes
- ----------
- toolmanager : `.ToolManager`
- The tools with which this `ToolContainer` wants to communicate.
- """
- _icon_extension = '.png'
- """
- Toolcontainer button icon image format extension
- **String**: Image extension
- """
- def __init__(self, toolmanager):
- self.toolmanager = toolmanager
- toolmanager.toolmanager_connect(
- 'tool_message_event',
- lambda event: self.set_message(event.message))
- toolmanager.toolmanager_connect(
- 'tool_removed_event',
- lambda event: self.remove_toolitem(event.tool.name))
- def _tool_toggled_cbk(self, event):
- """
- Capture the 'tool_trigger_[name]'
- This only gets used for toggled tools.
- """
- self.toggle_toolitem(event.tool.name, event.tool.toggled)
- def add_tool(self, tool, group, position=-1):
- """
- Add a tool to this container.
- Parameters
- ----------
- tool : tool_like
- The tool to add, see `.ToolManager.get_tool`.
- group : str
- The name of the group to add this tool to.
- position : int, default: -1
- The position within the group to place this tool.
- """
- tool = self.toolmanager.get_tool(tool)
- image = self._get_image_filename(tool.image)
- toggle = getattr(tool, 'toggled', None) is not None
- self.add_toolitem(tool.name, group, position,
- image, tool.description, toggle)
- if toggle:
- self.toolmanager.toolmanager_connect('tool_trigger_%s' % tool.name,
- self._tool_toggled_cbk)
- # If initially toggled
- if tool.toggled:
- self.toggle_toolitem(tool.name, True)
- def _get_image_filename(self, image):
- """Find the image based on its name."""
- if not image:
- return None
- basedir = cbook._get_data_path("images")
- for fname in [
- image,
- image + self._icon_extension,
- str(basedir / image),
- str(basedir / (image + self._icon_extension)),
- ]:
- if os.path.isfile(fname):
- return fname
- def trigger_tool(self, name):
- """
- Trigger the tool.
- Parameters
- ----------
- name : str
- Name (id) of the tool triggered from within the container.
- """
- self.toolmanager.trigger_tool(name, sender=self)
- def add_toolitem(self, name, group, position, image, description, toggle):
- """
- Add a toolitem to the container.
- This method must be implemented per backend.
- The callback associated with the button click event,
- must be *exactly* ``self.trigger_tool(name)``.
- Parameters
- ----------
- name : str
- Name of the tool to add, this gets used as the tool's ID and as the
- default label of the buttons.
- group : str
- Name of the group that this tool belongs to.
- position : int
- Position of the tool within its group, if -1 it goes at the end.
- image_file : str
- Filename of the image for the button or `None`.
- description : str
- Description of the tool, used for the tooltips.
- toggle : bool
- * `True` : The button is a toggle (change the pressed/unpressed
- state between consecutive clicks).
- * `False` : The button is a normal button (returns to unpressed
- state after release).
- """
- raise NotImplementedError
- def toggle_toolitem(self, name, toggled):
- """
- Toggle the toolitem without firing event.
- Parameters
- ----------
- name : str
- Id of the tool to toggle.
- toggled : bool
- Whether to set this tool as toggled or not.
- """
- raise NotImplementedError
- def remove_toolitem(self, name):
- """
- Remove a toolitem from the `ToolContainer`.
- This method must get implemented per backend.
- Called when `.ToolManager` emits a `tool_removed_event`.
- Parameters
- ----------
- name : str
- Name of the tool to remove.
- """
- raise NotImplementedError
- def set_message(self, s):
- """
- Display a message on the toolbar.
- Parameters
- ----------
- s : str
- Message text.
- """
- raise NotImplementedError
- @cbook.deprecated("3.3")
- class StatusbarBase:
- """Base class for the statusbar."""
- def __init__(self, toolmanager):
- self.toolmanager = toolmanager
- self.toolmanager.toolmanager_connect('tool_message_event',
- self._message_cbk)
- def _message_cbk(self, event):
- """Capture the 'tool_message_event' and set the message."""
- self.set_message(event.message)
- def set_message(self, s):
- """
- Display a message on toolbar or in status bar.
- Parameters
- ----------
- s : str
- Message text.
- """
- class _Backend:
- # A backend can be defined by using the following pattern:
- #
- # @_Backend.export
- # class FooBackend(_Backend):
- # # override the attributes and methods documented below.
- # `backend_version` may be overridden by the subclass.
- backend_version = "unknown"
- # The `FigureCanvas` class must be defined.
- FigureCanvas = None
- # For interactive backends, the `FigureManager` class must be overridden.
- FigureManager = FigureManagerBase
- # The following methods must be left as None for non-interactive backends.
- # For interactive backends, `trigger_manager_draw` should be a function
- # taking a manager as argument and triggering a canvas draw, and `mainloop`
- # should be a function taking no argument and starting the backend main
- # loop.
- trigger_manager_draw = None
- mainloop = None
- # The following methods will be automatically defined and exported, but
- # can be overridden.
- @classmethod
- def new_figure_manager(cls, num, *args, **kwargs):
- """Create a new figure manager instance."""
- # This import needs to happen here due to circular imports.
- from matplotlib.figure import Figure
- fig_cls = kwargs.pop('FigureClass', Figure)
- fig = fig_cls(*args, **kwargs)
- return cls.new_figure_manager_given_figure(num, fig)
- @classmethod
- def new_figure_manager_given_figure(cls, num, figure):
- """Create a new figure manager instance for the given figure."""
- canvas = cls.FigureCanvas(figure)
- manager = cls.FigureManager(canvas, num)
- return manager
- @classmethod
- def draw_if_interactive(cls):
- if cls.trigger_manager_draw is not None and is_interactive():
- manager = Gcf.get_active()
- if manager:
- cls.trigger_manager_draw(manager)
- @classmethod
- def show(cls, *, block=None):
- """
- Show all figures.
- `show` blocks by calling `mainloop` if *block* is ``True``, or if it
- is ``None`` and we are neither in IPython's ``%pylab`` mode, nor in
- `interactive` mode.
- """
- managers = Gcf.get_all_fig_managers()
- if not managers:
- return
- for manager in managers:
- try:
- manager.show() # Emits a warning for non-interactive backend.
- except NonGuiException as exc:
- cbook._warn_external(str(exc))
- if cls.mainloop is None:
- return
- if block is None:
- # Hack: Are we in IPython's pylab mode?
- from matplotlib import pyplot
- try:
- # IPython versions >= 0.10 tack the _needmain attribute onto
- # pyplot.show, and always set it to False, when in %pylab mode.
- ipython_pylab = not pyplot.show._needmain
- except AttributeError:
- ipython_pylab = False
- block = not ipython_pylab and not is_interactive()
- # TODO: The above is a hack to get the WebAgg backend working with
- # ipython's `%pylab` mode until proper integration is implemented.
- if get_backend() == "WebAgg":
- block = True
- if block:
- cls.mainloop()
- # This method is the one actually exporting the required methods.
- @staticmethod
- def export(cls):
- for name in [
- "backend_version",
- "FigureCanvas",
- "FigureManager",
- "new_figure_manager",
- "new_figure_manager_given_figure",
- "draw_if_interactive",
- "show",
- ]:
- setattr(sys.modules[cls.__module__], name, getattr(cls, name))
- # For back-compatibility, generate a shim `Show` class.
- class Show(ShowBase):
- def mainloop(self):
- return cls.mainloop()
- setattr(sys.modules[cls.__module__], "Show", Show)
- return cls
- class ShowBase(_Backend):
- """
- Simple base class to generate a ``show()`` function in backends.
- Subclass must override ``mainloop()`` method.
- """
- def __call__(self, block=None):
- return self.show(block=block)
|