1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421 |
- """
- Abstract base classes define the primitives that renderers and
- graphics contexts must implement to serve as a matplotlib backend
- :class:`RendererBase`
- An abstract base class to handle drawing/rendering operations.
- :class:`FigureCanvasBase`
- The abstraction layer that separates the
- :class:`matplotlib.figure.Figure` from the backend specific
- details like a user interface drawing area
- :class:`GraphicsContextBase`
- An abstract base class that provides color, line styles, etc...
- :class:`Event`
- The base class for all of the matplotlib event
- handling. Derived classes such as :class:`KeyEvent` and
- :class:`MouseEvent` store the meta data like keys and buttons
- pressed, x and y locations in pixel and
- :class:`~matplotlib.axes.Axes` coordinates.
- :class:`ShowBase`
- The base class for the Show class of each interactive backend;
- the 'show' callable is then set to Show.__call__, inherited from
- ShowBase.
- :class:`ToolContainerBase`
- The base class for the Toolbar class of each interactive backend.
- :class:`StatusbarBase`
- The base class for the messaging area.
- """
- from contextlib import contextmanager
- from enum import IntEnum
- import functools
- import importlib
- import io
- import logging
- import os
- import sys
- import time
- 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.transforms import Affine2D
- from matplotlib.path import Path
- try:
- from PIL import __version__ as PILLOW_VERSION
- from distutils.version import LooseVersion
- if LooseVersion(PILLOW_VERSION) >= "3.4":
- _has_pil = True
- else:
- _has_pil = False
- del PILLOW_VERSION
- except ImportError:
- _has_pil = False
- _log = logging.getLogger(__name__)
- _default_filetypes = {
- 'ps': 'Postscript',
- 'eps': 'Encapsulated Postscript',
- 'pdf': 'Portable Document Format',
- 'pgf': 'PGF code for LaTeX',
- 'png': 'Portable Network Graphics',
- 'raw': 'Raw RGBA bitmap',
- 'rgba': 'Raw RGBA bitmap',
- 'svg': 'Scalable Vector Graphics',
- 'svgz': 'Scalable Vector Graphics'
- }
- _default_backends = {
- 'ps': 'matplotlib.backends.backend_ps',
- 'eps': 'matplotlib.backends.backend_ps',
- 'pdf': 'matplotlib.backends.backend_pdf',
- 'pgf': 'matplotlib.backends.backend_pgf',
- 'png': 'matplotlib.backends.backend_agg',
- 'raw': 'matplotlib.backends.backend_agg',
- 'rgba': 'matplotlib.backends.backend_agg',
- 'svg': 'matplotlib.backends.backend_svg',
- 'svgz': 'matplotlib.backends.backend_svg',
- }
- 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):
- 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.
- 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 = [
- (path, transforms.Affine2D(transform))
- for path, transform in 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, path_ids, offsets,
- offsetTrans, facecolors, edgecolors, linewidths, linestyles,
- antialiaseds, urls, offset_position):
- path, transform = path_id
- transform = transforms.Affine2D(
- transform.get_matrix()).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):
- """
- This provides a 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
- ----------
- 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):
- """
- This is a helper method (along with :meth:`_iter_collection`) to make
- it easier to write a space-efficient :meth:`draw_path_collection`
- implementation in a backend.
- 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):
- """
- This is a helper method (along with
- :meth:`_iter_collection_raw_paths`) to make it easier to write
- a space-efficient :meth:`draw_path_collection` implementation in a
- backend.
- 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 (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 *may* be passed to :meth:`draw_image`. It takes the
- form of a :class:`~matplotlib.transforms.Affine2DBase` instance.
- 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
- 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 matplotlib 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
- :class:`~matplotlib.font_manager.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
- @cbook.deprecated("3.1", alternative="cbook.strip_math")
- def strip_math(self, s):
- return cbook.strip_math(s)
- 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.
- """
- 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 = None, 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 :class:`~matplotlib.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):
- """
- Returns 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
- -------
- sketch_params : 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.
- Attributes
- ----------
- interval : scalar
- The time between timer events in milliseconds. Default is 1000 ms.
- single_shot : bool
- Boolean flag indicating whether this timer should operate as single
- shot (run once and then stop). Defaults to `False`.
- callbacks : List[Tuple[callable, Tuple, Dict]]
- Stores list of (func, args, kwargs) tuples that will be called upon
- timer events. This list can be manipulated directly, or the
- functions `add_callback` and `remove_callback` can be used.
- """
- def __init__(self, interval=None, callbacks=None):
- #Initialize empty callbacks list and setup default settings if necssary
- if callbacks is None:
- self.callbacks = []
- else:
- self.callbacks = callbacks[:] # Create a copy
- if interval is None:
- self._interval = 1000
- else:
- self._interval = interval
- self._single = False
- # Default attribute for holding the GUI-specific timer object
- self._timer = None
- 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):
- 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):
- 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 :class:`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 :class:`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 :class:`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 :class:`Event` and :class:`LocationEvent`
- attributes, the following attributes are defined:
- Attributes
- ----------
- button : {None, MouseButton.LEFT, MouseButton.MIDDLE, MouseButton.RIGHT, \
- '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`.
- step : int
- 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'
- """
- LocationEvent.__init__(self, name, canvas, x, y, guiEvent=guiEvent)
- if button in MouseButton.__members__.values():
- button = MouseButton(button)
- self.button = button
- self.key = key
- self.step = step
- self.dblclick = dblclick
- 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 :class:`Event` attributes plus
- Attributes
- ----------
- mouseevent : `MouseEvent`
- the mouse event that generated the pick
- artist : `matplotlib.artist.Artist`
- the picked artist
- other
- extra class dependent attrs -- e.g., a
- :class:`~matplotlib.lines.Line2D` pick may define different
- extra attributes than a
- :class:`~matplotlib.collections.PatchCollection` pick event
- Examples
- --------
- ::
- 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 :class:`Event` and :class:`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):
- LocationEvent.__init__(self, name, canvas, x, y, guiEvent=guiEvent)
- self.key = key
- def _get_renderer(figure, print_method, *, draw_disabled=False):
- """
- Get the renderer that would be used to save a `~.Figure`, and cache it on
- the figure.
- If *draw_disabled* is True, additionally replace draw_foo methods on
- *renderer* by no-ops. This is used by the tight-bbox-saving renderer,
- which needs to walk through the artist tree to compute the tight-bbox, but
- for which the output file may be closed early.
- """
- # 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):
- try:
- print_method(io.BytesIO())
- except Done as exc:
- renderer, = figure._cachedRenderer, = exc.args
- if draw_disabled:
- for meth_name in dir(RendererBase):
- if meth_name.startswith("draw_"):
- setattr(renderer, meth_name, lambda *args, **kwargs: None)
- return renderer
- 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)
- class FigureCanvasBase:
- """
- The canvas the figure renders into.
- Public attributes
- 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',
- 'idle_event',
- 'figure_enter_event',
- 'figure_leave_event',
- 'axes_enter_event',
- 'axes_leave_event',
- 'close_event'
- ]
- fixed_dpi = None
- filetypes = _default_filetypes
- if _has_pil:
- # JPEG support
- register_backend('jpg', 'matplotlib.backends.backend_agg',
- 'Joint Photographic Experts Group')
- register_backend('jpeg', 'matplotlib.backends.backend_agg',
- 'Joint Photographic Experts Group')
- # TIFF support
- register_backend('tif', 'matplotlib.backends.backend_agg',
- 'Tagged Image File Format')
- register_backend('tiff', 'matplotlib.backends.backend_agg',
- 'Tagged Image File Format')
- @cbook._classproperty
- def supports_blit(cls):
- 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
- # 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):
- """
- Returns 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):
- """
- This method will be called by artists who are picked and will
- fire off :class:`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):
- """
- 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 be call all functions connected to the
- 'scroll_event' with a :class:`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):
- """
- 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 :class:`MouseEvent`.
- This method will be call all functions connected to the
- 'button_press_event' with a :class:`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):
- """
- 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 :class:`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):
- """
- 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 :class:`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):
- """
- 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):
- """
- 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', message='enter_notify_event expects a location but '
- 'your backend did not pass one.')
- 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 : tuple or list
- (x, y) coordinates.
- x position - pixels from left of canvas.
- y position - pixels from bottom of canvas.
- Returns
- -------
- axes : `~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 which are currently 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, ax.
- Usually called by the widgets.
- It is ok to call this even if you 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 :class:`~matplotlib.figure.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, fmt):
- """
- Return a canvas suitable for saving figures to a specified file format.
- If necessary, this function will switch to a registered backend that
- supports the format.
- """
- # Return the current canvas if it supports the requested format.
- if hasattr(self, 'print_{}'.format(fmt)):
- return self
- # 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, **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
- can also be a file object on image backends
- orientation : {'landscape', 'portrait'}, default: 'portrait'
- only currently applies to PostScript printing.
- dpi : scalar, optional
- the dots per inch to save the figure in; if None, use savefig.dpi
- facecolor : color, default: :rc:`savefig.facecolor`
- The facecolor of the figure.
- edgecolor : color, default: :rc:`savefig.edgecolor`
- The edgecolor of the figure.
- 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 `~matplotlib.transforms.Bbox`, \
- default: :rc:`savefig.bbox`
- Bbox 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.
- """
- 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(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, _is_saving=True, manager=None), \
- cbook._setattr_cm(self.figure, dpi=dpi):
- if facecolor is None:
- facecolor = rcParams['savefig.facecolor']
- if edgecolor is None:
- edgecolor = rcParams['savefig.edgecolor']
- origfacecolor = self.figure.get_facecolor()
- origedgecolor = self.figure.get_edgecolor()
- 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, dpi=dpi, orientation=orientation),
- draw_disabled=True)
- self.figure.draw(renderer)
- bbox_artists = kwargs.pop("bbox_extra_artists", None)
- bbox_inches = self.figure.get_tightbbox(renderer,
- bbox_extra_artists=bbox_artists)
- pad = kwargs.pop("pad_inches", None)
- if pad is None:
- pad = rcParams['savefig.pad_inches']
- bbox_inches = bbox_inches.padded(pad)
- # 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):
- """
- Get the default savefig file format as specified in rcParam
- ``savefig.format``. Returned string excludes period. Overridden
- in backends that only support a single file type.
- """
- return rcParams['savefig.format']
- def get_window_title(self):
- """
- Get the title text of the window containing the figure.
- Return None if there is no window (e.g., a PS backend).
- """
- if hasattr(self, "manager"):
- 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 hasattr(self, "manager"):
- 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)
- def new_timer(self, *args, **kwargs):
- """
- 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.
- Other Parameters
- ----------------
- interval : scalar
- 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 TimerBase(*args, **kwargs)
- 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 `ginput`
- and `waitforbuttonpress`, to wait for events.
- The event loop blocks until a callback function triggers
- `stop_event_loop`, or *timeout* is reached.
- If *timeout* is 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, toolbar=None):
- """
- Implement the default Matplotlib key bindings for the canvas and toolbar
- described at :ref:`key-event-handling`.
- Parameters
- ----------
- event : :class:`KeyEvent`
- a key press/release event
- canvas : :class:`FigureCanvasBase`
- the backend-specific canvas instance
- toolbar : :class:`NavigationToolbar2`
- the navigation cursor toolbar
- """
- # these bindings happen whether you are over an axes or not
- if event.key is None:
- return
- # 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']
- 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 = 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 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?
- 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?
- a.set_navigate(i == n)
- def button_press_handler(event, canvas, toolbar=None):
- """
- The default Matplotlib button actions for extra mouse buttons.
- """
- 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 : :class:`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 = 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()
- def show(self):
- """
- For GUI backends, show the figure window and redraw.
- For non-GUI backends, raise an exception to be caught
- by :meth:`~matplotlib.figure.Figure.show`, for an
- optional warning.
- """
- raise NonGuiException()
- 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, self.canvas, self.canvas.toolbar)
- def button_press(self, event):
- """The default Matplotlib button actions for extra mouse buttons."""
- if rcParams['toolbar'] != 'toolmanager':
- button_press_handler(event, self.canvas, self.canvas.toolbar)
- def get_window_title(self):
- """
- Get the title text of the window containing the figure.
- Return None for non-GUI (e.g., PS) backends.
- """
- 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 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:`_init_toolbar`
- create your toolbar widget
- :meth:`draw_rubberband` (optional)
- draw the zoom to rect "rubberband" rectangle
- :meth:`press` (optional)
- whenever a mouse button is pressed, you'll be notified with
- the event
- :meth:`release` (optional)
- whenever a mouse button is released, you'll be notified with
- the event
- :meth:`set_message` (optional)
- display message
- :meth:`set_history_buttons` (optional)
- you can change the history back / forward buttons to
- indicate disabled / enabled state.
- 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', 'Pan axes with left mouse, zoom with right', 'move', 'pan'),
- ('Zoom', 'Zoom to rectangle', '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
- self._idPress = None
- self._idRelease = None
- self._active = None
- # This cursor will be set after the initial draw.
- self._lastCursor = cursors.POINTER
- self._init_toolbar()
- self._idDrag = self.canvas.mpl_connect(
- 'motion_notify_event', self.mouse_move)
- self._ids_zoom = []
- self._zoom_mode = None
- self._button_pressed = None # determined by button pressed at start
- self.mode = '' # 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 back(self, *args):
- """Move back up the view lim stack."""
- self._nav_stack.back()
- self.set_history_buttons()
- self._update_view()
- 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 forward(self, *args):
- """Move forward in the view lim stack."""
- self._nav_stack.forward()
- self.set_history_buttons()
- self._update_view()
- def home(self, *args):
- """Restore the original view."""
- self._nav_stack.home()
- self.set_history_buttons()
- self._update_view()
- 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._active:
- if self._lastCursor != cursors.POINTER:
- self.set_cursor(cursors.POINTER)
- self._lastCursor = cursors.POINTER
- else:
- if (self._active == 'ZOOM'
- and self._lastCursor != cursors.SELECT_REGION):
- self.set_cursor(cursors.SELECT_REGION)
- self._lastCursor = cursors.SELECT_REGION
- elif (self._active == '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:
- 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)
- if data_str is not None:
- s = s + ' ' + data_str
- if len(self.mode):
- self.set_message('%s, %s' % (self.mode, s))
- else:
- self.set_message(s)
- else:
- self.set_message(self.mode)
- def pan(self, *args):
- """
- Activate the pan/zoom tool.
- Pan with left button, zoom with right.
- """
- # set the pointer icon and button press funcs to the
- # appropriate callbacks
- if self._active == 'PAN':
- self._active = None
- else:
- self._active = 'PAN'
- if self._idPress is not None:
- self._idPress = self.canvas.mpl_disconnect(self._idPress)
- self.mode = ''
- if self._idRelease is not None:
- self._idRelease = self.canvas.mpl_disconnect(self._idRelease)
- self.mode = ''
- if self._active:
- self._idPress = self.canvas.mpl_connect(
- 'button_press_event', self.press_pan)
- self._idRelease = self.canvas.mpl_connect(
- 'button_release_event', self.release_pan)
- self.mode = 'pan/zoom'
- self.canvas.widgetlock(self)
- else:
- self.canvas.widgetlock.release(self)
- for a in self.canvas.figure.get_axes():
- a.set_navigate_mode(self._active)
- self.set_message(self.mode)
- def press(self, event):
- """Called whenever a mouse button is pressed."""
- def press_pan(self, event):
- """Callback for mouse button press in pan/zoom mode."""
- if event.button == 1:
- self._button_pressed = 1
- elif event.button == 3:
- self._button_pressed = 3
- 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._idDrag)
- self._idDrag = self.canvas.mpl_connect('motion_notify_event',
- self.drag_pan)
- self.press(event)
- def press_zoom(self, event):
- """Callback for mouse button press in zoom to rect mode."""
- # If we're already in the middle of a zoom, pressing another
- # button works to "cancel"
- if self._ids_zoom != []:
- for zoom_id in self._ids_zoom:
- self.canvas.mpl_disconnect(zoom_id)
- self.release(event)
- self.draw()
- self._xypress = None
- self._button_pressed = None
- self._ids_zoom = []
- return
- if event.button == 1:
- self._button_pressed = 1
- elif event.button == 3:
- self._button_pressed = 3
- 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_zoom()):
- self._xypress.append((x, y, a, i, a._get_view()))
- id1 = self.canvas.mpl_connect('motion_notify_event', self.drag_zoom)
- id2 = self.canvas.mpl_connect('key_press_event',
- self._switch_on_zoom_mode)
- id3 = self.canvas.mpl_connect('key_release_event',
- self._switch_off_zoom_mode)
- self._ids_zoom = id1, id2, id3
- self._zoom_mode = event.key
- self.press(event)
- def _switch_on_zoom_mode(self, event):
- self._zoom_mode = event.key
- self.mouse_move(event)
- def _switch_off_zoom_mode(self, event):
- self._zoom_mode = None
- self.mouse_move(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()
- def release(self, event):
- """Callback for mouse button release."""
- 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._idDrag)
- self._idDrag = 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()
- self.release(event)
- self.draw()
- 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 drag_zoom(self, event):
- """Callback for dragging in zoom mode."""
- if self._xypress:
- x, y = event.x, event.y
- lastx, lasty, a, ind, view = self._xypress[0]
- (x1, y1), (x2, y2) = np.clip(
- [[lastx, lasty], [x, y]], a.bbox.min, a.bbox.max)
- if self._zoom_mode == "x":
- y1, y2 = a.bbox.intervaly
- elif self._zoom_mode == "y":
- x1, x2 = a.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."""
- for zoom_id in self._ids_zoom:
- self.canvas.mpl_disconnect(zoom_id)
- self._ids_zoom = []
- self.remove_rubberband()
- if not self._xypress:
- return
- last_a = []
- for cur_xypress in self._xypress:
- x, y = event.x, event.y
- lastx, lasty, a, ind, view = cur_xypress
- # 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 - lastx) < 5 and self._zoom_mode != "y") or
- (abs(y - lasty) < 5 and self._zoom_mode != "x")):
- self._xypress = None
- self.release(event)
- self.draw()
- return
- # detect twinx, twiny axes and avoid double zooming
- twinx, twiny = False, False
- if last_a:
- for la in last_a:
- if a.get_shared_x_axes().joined(a, la):
- twinx = True
- if a.get_shared_y_axes().joined(a, la):
- twiny = True
- last_a.append(a)
- if self._button_pressed == 1:
- direction = 'in'
- elif self._button_pressed == 3:
- direction = 'out'
- else:
- continue
- a._set_view_from_bbox((lastx, lasty, x, y), direction,
- self._zoom_mode, twinx, twiny)
- self.draw()
- self._xypress = None
- self._button_pressed = None
- self._zoom_mode = None
- self.push_current()
- self.release(event)
- def draw(self):
- """Redraw the canvases, update the locators."""
- 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:
- loc.refresh()
- 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 zoom(self, *args):
- """Activate zoom to rect mode."""
- if self._active == 'ZOOM':
- self._active = None
- else:
- self._active = 'ZOOM'
- if self._idPress is not None:
- self._idPress = self.canvas.mpl_disconnect(self._idPress)
- self.mode = ''
- if self._idRelease is not None:
- self._idRelease = self.canvas.mpl_disconnect(self._idRelease)
- self.mode = ''
- if self._active:
- self._idPress = self.canvas.mpl_connect('button_press_event',
- self.press_zoom)
- self._idRelease = self.canvas.mpl_connect('button_release_event',
- self.release_zoom)
- self.mode = 'zoom rect'
- self.canvas.widgetlock(self)
- else:
- self.canvas.widgetlock.release(self)
- for a in self.canvas.figure.get_axes():
- a.set_navigate_mode(self._active)
- self.set_message(self.mode)
- 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
- self.toolmanager.toolmanager_connect('tool_removed_event',
- self._remove_tool_cbk)
- def _tool_toggled_cbk(self, event):
- """
- Captures 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):
- """
- Adds 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 (optional)
- The position within the group to place this tool. Defaults to end.
- """
- 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 _remove_tool_cbk(self, event):
- """Captures the 'tool_removed_event' signal and removes the tool."""
- self.remove_toolitem(event.tool.name)
- 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 get 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 : String
- 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 : String
- Filename of the image for the button or `None`
- description : String
- 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 : String
- 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
- 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.
- """
- pass
- 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
- @cbook._make_keyword_only("3.1", "block")
- 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:
- # Emits a warning if the backend is non-interactive.
- manager.canvas.figure.show()
- 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)
|