patches.py 161 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052405340544055405640574058405940604061406240634064406540664067406840694070407140724073407440754076407740784079408040814082408340844085408640874088408940904091409240934094409540964097409840994100410141024103410441054106410741084109411041114112411341144115411641174118411941204121412241234124412541264127412841294130413141324133413441354136413741384139414041414142414341444145414641474148414941504151415241534154415541564157415841594160416141624163416441654166416741684169417041714172417341744175417641774178417941804181418241834184418541864187418841894190419141924193419441954196419741984199420042014202420342044205420642074208420942104211421242134214421542164217421842194220422142224223422442254226422742284229423042314232423342344235423642374238423942404241424242434244424542464247424842494250425142524253425442554256425742584259426042614262426342644265426642674268426942704271427242734274427542764277427842794280428142824283428442854286428742884289429042914292429342944295429642974298429943004301430243034304430543064307430843094310431143124313431443154316431743184319432043214322432343244325432643274328432943304331433243334334433543364337433843394340434143424343434443454346434743484349435043514352435343544355435643574358435943604361436243634364436543664367436843694370437143724373437443754376437743784379438043814382438343844385438643874388438943904391439243934394439543964397439843994400440144024403440444054406440744084409441044114412441344144415441644174418441944204421442244234424442544264427442844294430443144324433443444354436443744384439444044414442444344444445444644474448444944504451445244534454445544564457445844594460446144624463446444654466446744684469447044714472447344744475447644774478447944804481448244834484448544864487448844894490449144924493449444954496449744984499450045014502450345044505450645074508450945104511451245134514451545164517451845194520452145224523452445254526452745284529453045314532453345344535453645374538453945404541454245434544454545464547454845494550455145524553455445554556455745584559456045614562456345644565456645674568456945704571457245734574457545764577457845794580458145824583458445854586458745884589459045914592459345944595459645974598459946004601460246034604460546064607460846094610461146124613461446154616461746184619462046214622462346244625462646274628462946304631463246334634463546364637463846394640464146424643464446454646464746484649465046514652465346544655465646574658465946604661466246634664466546664667466846694670467146724673467446754676467746784679468046814682468346844685468646874688468946904691469246934694469546964697469846994700470147024703470447054706470747084709471047114712471347144715471647174718471947204721472247234724472547264727472847294730473147324733473447354736473747384739474047414742474347444745474647474748474947504751475247534754475547564757475847594760
  1. r"""
  2. Patches are `.Artist`\s with a face color and an edge color.
  3. """
  4. import functools
  5. import inspect
  6. import math
  7. from numbers import Number, Real
  8. import textwrap
  9. from types import SimpleNamespace
  10. from collections import namedtuple
  11. from matplotlib.transforms import Affine2D
  12. import numpy as np
  13. import matplotlib as mpl
  14. from . import (_api, artist, cbook, colors, _docstring, hatch as mhatch,
  15. lines as mlines, transforms)
  16. from .bezier import (
  17. NonIntersectingPathException, get_cos_sin, get_intersection,
  18. get_parallels, inside_circle, make_wedged_bezier2,
  19. split_bezier_intersecting_with_closedpath, split_path_inout)
  20. from .path import Path
  21. from ._enums import JoinStyle, CapStyle
  22. @_docstring.interpd
  23. @_api.define_aliases({
  24. "antialiased": ["aa"],
  25. "edgecolor": ["ec"],
  26. "facecolor": ["fc"],
  27. "linestyle": ["ls"],
  28. "linewidth": ["lw"],
  29. })
  30. class Patch(artist.Artist):
  31. """
  32. A patch is a 2D artist with a face color and an edge color.
  33. If any of *edgecolor*, *facecolor*, *linewidth*, or *antialiased*
  34. are *None*, they default to their rc params setting.
  35. """
  36. zorder = 1
  37. # Whether to draw an edge by default. Set on a
  38. # subclass-by-subclass basis.
  39. _edge_default = False
  40. def __init__(self, *,
  41. edgecolor=None,
  42. facecolor=None,
  43. color=None,
  44. linewidth=None,
  45. linestyle=None,
  46. antialiased=None,
  47. hatch=None,
  48. fill=True,
  49. capstyle=None,
  50. joinstyle=None,
  51. **kwargs):
  52. """
  53. The following kwarg properties are supported
  54. %(Patch:kwdoc)s
  55. """
  56. super().__init__()
  57. if linestyle is None:
  58. linestyle = "solid"
  59. if capstyle is None:
  60. capstyle = CapStyle.butt
  61. if joinstyle is None:
  62. joinstyle = JoinStyle.miter
  63. self._hatch_color = colors.to_rgba(mpl.rcParams['hatch.color'])
  64. self._hatch_linewidth = mpl.rcParams['hatch.linewidth']
  65. self._fill = bool(fill) # needed for set_facecolor call
  66. if color is not None:
  67. if edgecolor is not None or facecolor is not None:
  68. _api.warn_external(
  69. "Setting the 'color' property will override "
  70. "the edgecolor or facecolor properties.")
  71. self.set_color(color)
  72. else:
  73. self.set_edgecolor(edgecolor)
  74. self.set_facecolor(facecolor)
  75. self._linewidth = 0
  76. self._unscaled_dash_pattern = (0, None) # offset, dash
  77. self._dash_pattern = (0, None) # offset, dash (scaled by linewidth)
  78. self.set_linestyle(linestyle)
  79. self.set_linewidth(linewidth)
  80. self.set_antialiased(antialiased)
  81. self.set_hatch(hatch)
  82. self.set_capstyle(capstyle)
  83. self.set_joinstyle(joinstyle)
  84. if len(kwargs):
  85. self._internal_update(kwargs)
  86. def get_verts(self):
  87. """
  88. Return a copy of the vertices used in this patch.
  89. If the patch contains Bézier curves, the curves will be interpolated by
  90. line segments. To access the curves as curves, use `get_path`.
  91. """
  92. trans = self.get_transform()
  93. path = self.get_path()
  94. polygons = path.to_polygons(trans)
  95. if len(polygons):
  96. return polygons[0]
  97. return []
  98. def _process_radius(self, radius):
  99. if radius is not None:
  100. return radius
  101. if isinstance(self._picker, Number):
  102. _radius = self._picker
  103. else:
  104. if self.get_edgecolor()[3] == 0:
  105. _radius = 0
  106. else:
  107. _radius = self.get_linewidth()
  108. return _radius
  109. def contains(self, mouseevent, radius=None):
  110. """
  111. Test whether the mouse event occurred in the patch.
  112. Parameters
  113. ----------
  114. mouseevent : `~matplotlib.backend_bases.MouseEvent`
  115. Where the user clicked.
  116. radius : float, optional
  117. Additional margin on the patch in target coordinates of
  118. `.Patch.get_transform`. See `.Path.contains_point` for further
  119. details.
  120. If `None`, the default value depends on the state of the object:
  121. - If `.Artist.get_picker` is a number, the default
  122. is that value. This is so that picking works as expected.
  123. - Otherwise if the edge color has a non-zero alpha, the default
  124. is half of the linewidth. This is so that all the colored
  125. pixels are "in" the patch.
  126. - Finally, if the edge has 0 alpha, the default is 0. This is
  127. so that patches without a stroked edge do not have points
  128. outside of the filled region report as "in" due to an
  129. invisible edge.
  130. Returns
  131. -------
  132. (bool, empty dict)
  133. """
  134. if self._different_canvas(mouseevent):
  135. return False, {}
  136. radius = self._process_radius(radius)
  137. codes = self.get_path().codes
  138. if codes is not None:
  139. vertices = self.get_path().vertices
  140. # if the current path is concatenated by multiple sub paths.
  141. # get the indexes of the starting code(MOVETO) of all sub paths
  142. idxs, = np.where(codes == Path.MOVETO)
  143. # Don't split before the first MOVETO.
  144. idxs = idxs[1:]
  145. subpaths = map(
  146. Path, np.split(vertices, idxs), np.split(codes, idxs))
  147. else:
  148. subpaths = [self.get_path()]
  149. inside = any(
  150. subpath.contains_point(
  151. (mouseevent.x, mouseevent.y), self.get_transform(), radius)
  152. for subpath in subpaths)
  153. return inside, {}
  154. def contains_point(self, point, radius=None):
  155. """
  156. Return whether the given point is inside the patch.
  157. Parameters
  158. ----------
  159. point : (float, float)
  160. The point (x, y) to check, in target coordinates of
  161. ``.Patch.get_transform()``. These are display coordinates for patches
  162. that are added to a figure or Axes.
  163. radius : float, optional
  164. Additional margin on the patch in target coordinates of
  165. `.Patch.get_transform`. See `.Path.contains_point` for further
  166. details.
  167. If `None`, the default value depends on the state of the object:
  168. - If `.Artist.get_picker` is a number, the default
  169. is that value. This is so that picking works as expected.
  170. - Otherwise if the edge color has a non-zero alpha, the default
  171. is half of the linewidth. This is so that all the colored
  172. pixels are "in" the patch.
  173. - Finally, if the edge has 0 alpha, the default is 0. This is
  174. so that patches without a stroked edge do not have points
  175. outside of the filled region report as "in" due to an
  176. invisible edge.
  177. Returns
  178. -------
  179. bool
  180. Notes
  181. -----
  182. The proper use of this method depends on the transform of the patch.
  183. Isolated patches do not have a transform. In this case, the patch
  184. creation coordinates and the point coordinates match. The following
  185. example checks that the center of a circle is within the circle
  186. >>> center = 0, 0
  187. >>> c = Circle(center, radius=1)
  188. >>> c.contains_point(center)
  189. True
  190. The convention of checking against the transformed patch stems from
  191. the fact that this method is predominantly used to check if display
  192. coordinates (e.g. from mouse events) are within the patch. If you want
  193. to do the above check with data coordinates, you have to properly
  194. transform them first:
  195. >>> center = 0, 0
  196. >>> c = Circle(center, radius=3)
  197. >>> plt.gca().add_patch(c)
  198. >>> transformed_interior_point = c.get_data_transform().transform((0, 2))
  199. >>> c.contains_point(transformed_interior_point)
  200. True
  201. """
  202. radius = self._process_radius(radius)
  203. return self.get_path().contains_point(point,
  204. self.get_transform(),
  205. radius)
  206. def contains_points(self, points, radius=None):
  207. """
  208. Return whether the given points are inside the patch.
  209. Parameters
  210. ----------
  211. points : (N, 2) array
  212. The points to check, in target coordinates of
  213. ``self.get_transform()``. These are display coordinates for patches
  214. that are added to a figure or Axes. Columns contain x and y values.
  215. radius : float, optional
  216. Additional margin on the patch in target coordinates of
  217. `.Patch.get_transform`. See `.Path.contains_point` for further
  218. details.
  219. If `None`, the default value depends on the state of the object:
  220. - If `.Artist.get_picker` is a number, the default
  221. is that value. This is so that picking works as expected.
  222. - Otherwise if the edge color has a non-zero alpha, the default
  223. is half of the linewidth. This is so that all the colored
  224. pixels are "in" the patch.
  225. - Finally, if the edge has 0 alpha, the default is 0. This is
  226. so that patches without a stroked edge do not have points
  227. outside of the filled region report as "in" due to an
  228. invisible edge.
  229. Returns
  230. -------
  231. length-N bool array
  232. Notes
  233. -----
  234. The proper use of this method depends on the transform of the patch.
  235. See the notes on `.Patch.contains_point`.
  236. """
  237. radius = self._process_radius(radius)
  238. return self.get_path().contains_points(points,
  239. self.get_transform(),
  240. radius)
  241. def update_from(self, other):
  242. # docstring inherited.
  243. super().update_from(other)
  244. # For some properties we don't need or don't want to go through the
  245. # getters/setters, so we just copy them directly.
  246. self._edgecolor = other._edgecolor
  247. self._facecolor = other._facecolor
  248. self._original_edgecolor = other._original_edgecolor
  249. self._original_facecolor = other._original_facecolor
  250. self._fill = other._fill
  251. self._hatch = other._hatch
  252. self._hatch_color = other._hatch_color
  253. self._unscaled_dash_pattern = other._unscaled_dash_pattern
  254. self.set_linewidth(other._linewidth) # also sets scaled dashes
  255. self.set_transform(other.get_data_transform())
  256. # If the transform of other needs further initialization, then it will
  257. # be the case for this artist too.
  258. self._transformSet = other.is_transform_set()
  259. def get_extents(self):
  260. """
  261. Return the `Patch`'s axis-aligned extents as a `~.transforms.Bbox`.
  262. """
  263. return self.get_path().get_extents(self.get_transform())
  264. def get_transform(self):
  265. """Return the `~.transforms.Transform` applied to the `Patch`."""
  266. return self.get_patch_transform() + artist.Artist.get_transform(self)
  267. def get_data_transform(self):
  268. """
  269. Return the `~.transforms.Transform` mapping data coordinates to
  270. physical coordinates.
  271. """
  272. return artist.Artist.get_transform(self)
  273. def get_patch_transform(self):
  274. """
  275. Return the `~.transforms.Transform` instance mapping patch coordinates
  276. to data coordinates.
  277. For example, one may define a patch of a circle which represents a
  278. radius of 5 by providing coordinates for a unit circle, and a
  279. transform which scales the coordinates (the patch coordinate) by 5.
  280. """
  281. return transforms.IdentityTransform()
  282. def get_antialiased(self):
  283. """Return whether antialiasing is used for drawing."""
  284. return self._antialiased
  285. def get_edgecolor(self):
  286. """Return the edge color."""
  287. return self._edgecolor
  288. def get_facecolor(self):
  289. """Return the face color."""
  290. return self._facecolor
  291. def get_linewidth(self):
  292. """Return the line width in points."""
  293. return self._linewidth
  294. def get_linestyle(self):
  295. """Return the linestyle."""
  296. return self._linestyle
  297. def set_antialiased(self, aa):
  298. """
  299. Set whether to use antialiased rendering.
  300. Parameters
  301. ----------
  302. aa : bool or None
  303. """
  304. if aa is None:
  305. aa = mpl.rcParams['patch.antialiased']
  306. self._antialiased = aa
  307. self.stale = True
  308. def _set_edgecolor(self, color):
  309. set_hatch_color = True
  310. if color is None:
  311. if (mpl.rcParams['patch.force_edgecolor'] or
  312. not self._fill or self._edge_default):
  313. color = mpl.rcParams['patch.edgecolor']
  314. else:
  315. color = 'none'
  316. set_hatch_color = False
  317. self._edgecolor = colors.to_rgba(color, self._alpha)
  318. if set_hatch_color:
  319. self._hatch_color = self._edgecolor
  320. self.stale = True
  321. def set_edgecolor(self, color):
  322. """
  323. Set the patch edge color.
  324. Parameters
  325. ----------
  326. color : :mpltype:`color` or None
  327. """
  328. self._original_edgecolor = color
  329. self._set_edgecolor(color)
  330. def _set_facecolor(self, color):
  331. if color is None:
  332. color = mpl.rcParams['patch.facecolor']
  333. alpha = self._alpha if self._fill else 0
  334. self._facecolor = colors.to_rgba(color, alpha)
  335. self.stale = True
  336. def set_facecolor(self, color):
  337. """
  338. Set the patch face color.
  339. Parameters
  340. ----------
  341. color : :mpltype:`color` or None
  342. """
  343. self._original_facecolor = color
  344. self._set_facecolor(color)
  345. def set_color(self, c):
  346. """
  347. Set both the edgecolor and the facecolor.
  348. Parameters
  349. ----------
  350. c : :mpltype:`color`
  351. See Also
  352. --------
  353. Patch.set_facecolor, Patch.set_edgecolor
  354. For setting the edge or face color individually.
  355. """
  356. self.set_facecolor(c)
  357. self.set_edgecolor(c)
  358. def set_alpha(self, alpha):
  359. # docstring inherited
  360. super().set_alpha(alpha)
  361. self._set_facecolor(self._original_facecolor)
  362. self._set_edgecolor(self._original_edgecolor)
  363. # stale is already True
  364. def set_linewidth(self, w):
  365. """
  366. Set the patch linewidth in points.
  367. Parameters
  368. ----------
  369. w : float or None
  370. """
  371. if w is None:
  372. w = mpl.rcParams['patch.linewidth']
  373. self._linewidth = float(w)
  374. self._dash_pattern = mlines._scale_dashes(
  375. *self._unscaled_dash_pattern, w)
  376. self.stale = True
  377. def set_linestyle(self, ls):
  378. """
  379. Set the patch linestyle.
  380. ========================================== =================
  381. linestyle description
  382. ========================================== =================
  383. ``'-'`` or ``'solid'`` solid line
  384. ``'--'`` or ``'dashed'`` dashed line
  385. ``'-.'`` or ``'dashdot'`` dash-dotted line
  386. ``':'`` or ``'dotted'`` dotted line
  387. ``'none'``, ``'None'``, ``' '``, or ``''`` draw nothing
  388. ========================================== =================
  389. Alternatively a dash tuple of the following form can be provided::
  390. (offset, onoffseq)
  391. where ``onoffseq`` is an even length tuple of on and off ink in points.
  392. Parameters
  393. ----------
  394. ls : {'-', '--', '-.', ':', '', (offset, on-off-seq), ...}
  395. The line style.
  396. """
  397. if ls is None:
  398. ls = "solid"
  399. if ls in [' ', '', 'none']:
  400. ls = 'None'
  401. self._linestyle = ls
  402. self._unscaled_dash_pattern = mlines._get_dash_pattern(ls)
  403. self._dash_pattern = mlines._scale_dashes(
  404. *self._unscaled_dash_pattern, self._linewidth)
  405. self.stale = True
  406. def set_fill(self, b):
  407. """
  408. Set whether to fill the patch.
  409. Parameters
  410. ----------
  411. b : bool
  412. """
  413. self._fill = bool(b)
  414. self._set_facecolor(self._original_facecolor)
  415. self._set_edgecolor(self._original_edgecolor)
  416. self.stale = True
  417. def get_fill(self):
  418. """Return whether the patch is filled."""
  419. return self._fill
  420. # Make fill a property so as to preserve the long-standing
  421. # but somewhat inconsistent behavior in which fill was an
  422. # attribute.
  423. fill = property(get_fill, set_fill)
  424. @_docstring.interpd
  425. def set_capstyle(self, s):
  426. """
  427. Set the `.CapStyle`.
  428. The default capstyle is 'round' for `.FancyArrowPatch` and 'butt' for
  429. all other patches.
  430. Parameters
  431. ----------
  432. s : `.CapStyle` or %(CapStyle)s
  433. """
  434. cs = CapStyle(s)
  435. self._capstyle = cs
  436. self.stale = True
  437. def get_capstyle(self):
  438. """Return the capstyle."""
  439. return self._capstyle.name
  440. @_docstring.interpd
  441. def set_joinstyle(self, s):
  442. """
  443. Set the `.JoinStyle`.
  444. The default joinstyle is 'round' for `.FancyArrowPatch` and 'miter' for
  445. all other patches.
  446. Parameters
  447. ----------
  448. s : `.JoinStyle` or %(JoinStyle)s
  449. """
  450. js = JoinStyle(s)
  451. self._joinstyle = js
  452. self.stale = True
  453. def get_joinstyle(self):
  454. """Return the joinstyle."""
  455. return self._joinstyle.name
  456. def set_hatch(self, hatch):
  457. r"""
  458. Set the hatching pattern.
  459. *hatch* can be one of::
  460. / - diagonal hatching
  461. \ - back diagonal
  462. | - vertical
  463. - - horizontal
  464. + - crossed
  465. x - crossed diagonal
  466. o - small circle
  467. O - large circle
  468. . - dots
  469. * - stars
  470. Letters can be combined, in which case all the specified
  471. hatchings are done. If same letter repeats, it increases the
  472. density of hatching of that pattern.
  473. Parameters
  474. ----------
  475. hatch : {'/', '\\', '|', '-', '+', 'x', 'o', 'O', '.', '*'}
  476. """
  477. # Use validate_hatch(list) after deprecation.
  478. mhatch._validate_hatch_pattern(hatch)
  479. self._hatch = hatch
  480. self.stale = True
  481. def get_hatch(self):
  482. """Return the hatching pattern."""
  483. return self._hatch
  484. def set_hatch_linewidth(self, lw):
  485. """Set the hatch linewidth."""
  486. self._hatch_linewidth = lw
  487. def get_hatch_linewidth(self):
  488. """Return the hatch linewidth."""
  489. return self._hatch_linewidth
  490. def _draw_paths_with_artist_properties(
  491. self, renderer, draw_path_args_list):
  492. """
  493. ``draw()`` helper factored out for sharing with `FancyArrowPatch`.
  494. Configure *renderer* and the associated graphics context *gc*
  495. from the artist properties, then repeatedly call
  496. ``renderer.draw_path(gc, *draw_path_args)`` for each tuple
  497. *draw_path_args* in *draw_path_args_list*.
  498. """
  499. renderer.open_group('patch', self.get_gid())
  500. gc = renderer.new_gc()
  501. gc.set_foreground(self._edgecolor, isRGBA=True)
  502. lw = self._linewidth
  503. if self._edgecolor[3] == 0 or self._linestyle == 'None':
  504. lw = 0
  505. gc.set_linewidth(lw)
  506. gc.set_dashes(*self._dash_pattern)
  507. gc.set_capstyle(self._capstyle)
  508. gc.set_joinstyle(self._joinstyle)
  509. gc.set_antialiased(self._antialiased)
  510. self._set_gc_clip(gc)
  511. gc.set_url(self._url)
  512. gc.set_snap(self.get_snap())
  513. gc.set_alpha(self._alpha)
  514. if self._hatch:
  515. gc.set_hatch(self._hatch)
  516. gc.set_hatch_color(self._hatch_color)
  517. gc.set_hatch_linewidth(self._hatch_linewidth)
  518. if self.get_sketch_params() is not None:
  519. gc.set_sketch_params(*self.get_sketch_params())
  520. if self.get_path_effects():
  521. from matplotlib.patheffects import PathEffectRenderer
  522. renderer = PathEffectRenderer(self.get_path_effects(), renderer)
  523. for draw_path_args in draw_path_args_list:
  524. renderer.draw_path(gc, *draw_path_args)
  525. gc.restore()
  526. renderer.close_group('patch')
  527. self.stale = False
  528. @artist.allow_rasterization
  529. def draw(self, renderer):
  530. # docstring inherited
  531. if not self.get_visible():
  532. return
  533. path = self.get_path()
  534. transform = self.get_transform()
  535. tpath = transform.transform_path_non_affine(path)
  536. affine = transform.get_affine()
  537. self._draw_paths_with_artist_properties(
  538. renderer,
  539. [(tpath, affine,
  540. # Work around a bug in the PDF and SVG renderers, which
  541. # do not draw the hatches if the facecolor is fully
  542. # transparent, but do if it is None.
  543. self._facecolor if self._facecolor[3] else None)])
  544. def get_path(self):
  545. """Return the path of this patch."""
  546. raise NotImplementedError('Derived must override')
  547. def get_window_extent(self, renderer=None):
  548. return self.get_path().get_extents(self.get_transform())
  549. def _convert_xy_units(self, xy):
  550. """Convert x and y units for a tuple (x, y)."""
  551. x = self.convert_xunits(xy[0])
  552. y = self.convert_yunits(xy[1])
  553. return x, y
  554. class Shadow(Patch):
  555. def __str__(self):
  556. return f"Shadow({self.patch})"
  557. @_docstring.interpd
  558. def __init__(self, patch, ox, oy, *, shade=0.7, **kwargs):
  559. """
  560. Create a shadow of the given *patch*.
  561. By default, the shadow will have the same face color as the *patch*,
  562. but darkened. The darkness can be controlled by *shade*.
  563. Parameters
  564. ----------
  565. patch : `~matplotlib.patches.Patch`
  566. The patch to create the shadow for.
  567. ox, oy : float
  568. The shift of the shadow in data coordinates, scaled by a factor
  569. of dpi/72.
  570. shade : float, default: 0.7
  571. How the darkness of the shadow relates to the original color. If 1, the
  572. shadow is black, if 0, the shadow has the same color as the *patch*.
  573. .. versionadded:: 3.8
  574. **kwargs
  575. Properties of the shadow patch. Supported keys are:
  576. %(Patch:kwdoc)s
  577. """
  578. super().__init__()
  579. self.patch = patch
  580. self._ox, self._oy = ox, oy
  581. self._shadow_transform = transforms.Affine2D()
  582. self.update_from(self.patch)
  583. if not 0 <= shade <= 1:
  584. raise ValueError("shade must be between 0 and 1.")
  585. color = (1 - shade) * np.asarray(colors.to_rgb(self.patch.get_facecolor()))
  586. self.update({'facecolor': color, 'edgecolor': color, 'alpha': 0.5,
  587. # Place shadow patch directly behind the inherited patch.
  588. 'zorder': np.nextafter(self.patch.zorder, -np.inf),
  589. **kwargs})
  590. def _update_transform(self, renderer):
  591. ox = renderer.points_to_pixels(self._ox)
  592. oy = renderer.points_to_pixels(self._oy)
  593. self._shadow_transform.clear().translate(ox, oy)
  594. def get_path(self):
  595. return self.patch.get_path()
  596. def get_patch_transform(self):
  597. return self.patch.get_patch_transform() + self._shadow_transform
  598. def draw(self, renderer):
  599. self._update_transform(renderer)
  600. super().draw(renderer)
  601. class Rectangle(Patch):
  602. """
  603. A rectangle defined via an anchor point *xy* and its *width* and *height*.
  604. The rectangle extends from ``xy[0]`` to ``xy[0] + width`` in x-direction
  605. and from ``xy[1]`` to ``xy[1] + height`` in y-direction. ::
  606. : +------------------+
  607. : | |
  608. : height |
  609. : | |
  610. : (xy)---- width -----+
  611. One may picture *xy* as the bottom left corner, but which corner *xy* is
  612. actually depends on the direction of the axis and the sign of *width*
  613. and *height*; e.g. *xy* would be the bottom right corner if the x-axis
  614. was inverted or if *width* was negative.
  615. """
  616. def __str__(self):
  617. pars = self._x0, self._y0, self._width, self._height, self.angle
  618. fmt = "Rectangle(xy=(%g, %g), width=%g, height=%g, angle=%g)"
  619. return fmt % pars
  620. @_docstring.interpd
  621. def __init__(self, xy, width, height, *,
  622. angle=0.0, rotation_point='xy', **kwargs):
  623. """
  624. Parameters
  625. ----------
  626. xy : (float, float)
  627. The anchor point.
  628. width : float
  629. Rectangle width.
  630. height : float
  631. Rectangle height.
  632. angle : float, default: 0
  633. Rotation in degrees anti-clockwise about the rotation point.
  634. rotation_point : {'xy', 'center', (number, number)}, default: 'xy'
  635. If ``'xy'``, rotate around the anchor point. If ``'center'`` rotate
  636. around the center. If 2-tuple of number, rotate around this
  637. coordinate.
  638. Other Parameters
  639. ----------------
  640. **kwargs : `~matplotlib.patches.Patch` properties
  641. %(Patch:kwdoc)s
  642. """
  643. super().__init__(**kwargs)
  644. self._x0 = xy[0]
  645. self._y0 = xy[1]
  646. self._width = width
  647. self._height = height
  648. self.angle = float(angle)
  649. self.rotation_point = rotation_point
  650. # Required for RectangleSelector with axes aspect ratio != 1
  651. # The patch is defined in data coordinates and when changing the
  652. # selector with square modifier and not in data coordinates, we need
  653. # to correct for the aspect ratio difference between the data and
  654. # display coordinate systems. Its value is typically provide by
  655. # Axes._get_aspect_ratio()
  656. self._aspect_ratio_correction = 1.0
  657. self._convert_units() # Validate the inputs.
  658. def get_path(self):
  659. """Return the vertices of the rectangle."""
  660. return Path.unit_rectangle()
  661. def _convert_units(self):
  662. """Convert bounds of the rectangle."""
  663. x0 = self.convert_xunits(self._x0)
  664. y0 = self.convert_yunits(self._y0)
  665. x1 = self.convert_xunits(self._x0 + self._width)
  666. y1 = self.convert_yunits(self._y0 + self._height)
  667. return x0, y0, x1, y1
  668. def get_patch_transform(self):
  669. # Note: This cannot be called until after this has been added to
  670. # an Axes, otherwise unit conversion will fail. This makes it very
  671. # important to call the accessor method and not directly access the
  672. # transformation member variable.
  673. bbox = self.get_bbox()
  674. if self.rotation_point == 'center':
  675. width, height = bbox.x1 - bbox.x0, bbox.y1 - bbox.y0
  676. rotation_point = bbox.x0 + width / 2., bbox.y0 + height / 2.
  677. elif self.rotation_point == 'xy':
  678. rotation_point = bbox.x0, bbox.y0
  679. else:
  680. rotation_point = self.rotation_point
  681. return transforms.BboxTransformTo(bbox) \
  682. + transforms.Affine2D() \
  683. .translate(-rotation_point[0], -rotation_point[1]) \
  684. .scale(1, self._aspect_ratio_correction) \
  685. .rotate_deg(self.angle) \
  686. .scale(1, 1 / self._aspect_ratio_correction) \
  687. .translate(*rotation_point)
  688. @property
  689. def rotation_point(self):
  690. """The rotation point of the patch."""
  691. return self._rotation_point
  692. @rotation_point.setter
  693. def rotation_point(self, value):
  694. if value in ['center', 'xy'] or (
  695. isinstance(value, tuple) and len(value) == 2 and
  696. isinstance(value[0], Real) and isinstance(value[1], Real)
  697. ):
  698. self._rotation_point = value
  699. else:
  700. raise ValueError("`rotation_point` must be one of "
  701. "{'xy', 'center', (number, number)}.")
  702. def get_x(self):
  703. """Return the left coordinate of the rectangle."""
  704. return self._x0
  705. def get_y(self):
  706. """Return the bottom coordinate of the rectangle."""
  707. return self._y0
  708. def get_xy(self):
  709. """Return the left and bottom coords of the rectangle as a tuple."""
  710. return self._x0, self._y0
  711. def get_corners(self):
  712. """
  713. Return the corners of the rectangle, moving anti-clockwise from
  714. (x0, y0).
  715. """
  716. return self.get_patch_transform().transform(
  717. [(0, 0), (1, 0), (1, 1), (0, 1)])
  718. def get_center(self):
  719. """Return the centre of the rectangle."""
  720. return self.get_patch_transform().transform((0.5, 0.5))
  721. def get_width(self):
  722. """Return the width of the rectangle."""
  723. return self._width
  724. def get_height(self):
  725. """Return the height of the rectangle."""
  726. return self._height
  727. def get_angle(self):
  728. """Get the rotation angle in degrees."""
  729. return self.angle
  730. def set_x(self, x):
  731. """Set the left coordinate of the rectangle."""
  732. self._x0 = x
  733. self.stale = True
  734. def set_y(self, y):
  735. """Set the bottom coordinate of the rectangle."""
  736. self._y0 = y
  737. self.stale = True
  738. def set_angle(self, angle):
  739. """
  740. Set the rotation angle in degrees.
  741. The rotation is performed anti-clockwise around *xy*.
  742. """
  743. self.angle = angle
  744. self.stale = True
  745. def set_xy(self, xy):
  746. """
  747. Set the left and bottom coordinates of the rectangle.
  748. Parameters
  749. ----------
  750. xy : (float, float)
  751. """
  752. self._x0, self._y0 = xy
  753. self.stale = True
  754. def set_width(self, w):
  755. """Set the width of the rectangle."""
  756. self._width = w
  757. self.stale = True
  758. def set_height(self, h):
  759. """Set the height of the rectangle."""
  760. self._height = h
  761. self.stale = True
  762. def set_bounds(self, *args):
  763. """
  764. Set the bounds of the rectangle as *left*, *bottom*, *width*, *height*.
  765. The values may be passed as separate parameters or as a tuple::
  766. set_bounds(left, bottom, width, height)
  767. set_bounds((left, bottom, width, height))
  768. .. ACCEPTS: (left, bottom, width, height)
  769. """
  770. if len(args) == 1:
  771. l, b, w, h = args[0]
  772. else:
  773. l, b, w, h = args
  774. self._x0 = l
  775. self._y0 = b
  776. self._width = w
  777. self._height = h
  778. self.stale = True
  779. def get_bbox(self):
  780. """Return the `.Bbox`."""
  781. return transforms.Bbox.from_extents(*self._convert_units())
  782. xy = property(get_xy, set_xy)
  783. class RegularPolygon(Patch):
  784. """A regular polygon patch."""
  785. def __str__(self):
  786. s = "RegularPolygon((%g, %g), %d, radius=%g, orientation=%g)"
  787. return s % (self.xy[0], self.xy[1], self.numvertices, self.radius,
  788. self.orientation)
  789. @_docstring.interpd
  790. def __init__(self, xy, numVertices, *,
  791. radius=5, orientation=0, **kwargs):
  792. """
  793. Parameters
  794. ----------
  795. xy : (float, float)
  796. The center position.
  797. numVertices : int
  798. The number of vertices.
  799. radius : float
  800. The distance from the center to each of the vertices.
  801. orientation : float
  802. The polygon rotation angle (in radians).
  803. **kwargs
  804. `Patch` properties:
  805. %(Patch:kwdoc)s
  806. """
  807. self.xy = xy
  808. self.numvertices = numVertices
  809. self.orientation = orientation
  810. self.radius = radius
  811. self._path = Path.unit_regular_polygon(numVertices)
  812. self._patch_transform = transforms.Affine2D()
  813. super().__init__(**kwargs)
  814. def get_path(self):
  815. return self._path
  816. def get_patch_transform(self):
  817. return self._patch_transform.clear() \
  818. .scale(self.radius) \
  819. .rotate(self.orientation) \
  820. .translate(*self.xy)
  821. class PathPatch(Patch):
  822. """A general polycurve path patch."""
  823. _edge_default = True
  824. def __str__(self):
  825. s = "PathPatch%d((%g, %g) ...)"
  826. return s % (len(self._path.vertices), *tuple(self._path.vertices[0]))
  827. @_docstring.interpd
  828. def __init__(self, path, **kwargs):
  829. """
  830. *path* is a `.Path` object.
  831. Valid keyword arguments are:
  832. %(Patch:kwdoc)s
  833. """
  834. super().__init__(**kwargs)
  835. self._path = path
  836. def get_path(self):
  837. return self._path
  838. def set_path(self, path):
  839. self._path = path
  840. class StepPatch(PathPatch):
  841. """
  842. A path patch describing a stepwise constant function.
  843. By default, the path is not closed and starts and stops at
  844. baseline value.
  845. """
  846. _edge_default = False
  847. @_docstring.interpd
  848. def __init__(self, values, edges, *,
  849. orientation='vertical', baseline=0, **kwargs):
  850. """
  851. Parameters
  852. ----------
  853. values : array-like
  854. The step heights.
  855. edges : array-like
  856. The edge positions, with ``len(edges) == len(vals) + 1``,
  857. between which the curve takes on vals values.
  858. orientation : {'vertical', 'horizontal'}, default: 'vertical'
  859. The direction of the steps. Vertical means that *values* are
  860. along the y-axis, and edges are along the x-axis.
  861. baseline : float, array-like or None, default: 0
  862. The bottom value of the bounding edges or when
  863. ``fill=True``, position of lower edge. If *fill* is
  864. True or an array is passed to *baseline*, a closed
  865. path is drawn.
  866. **kwargs
  867. `Patch` properties:
  868. %(Patch:kwdoc)s
  869. """
  870. self.orientation = orientation
  871. self._edges = np.asarray(edges)
  872. self._values = np.asarray(values)
  873. self._baseline = np.asarray(baseline) if baseline is not None else None
  874. self._update_path()
  875. super().__init__(self._path, **kwargs)
  876. def _update_path(self):
  877. if np.isnan(np.sum(self._edges)):
  878. raise ValueError('Nan values in "edges" are disallowed')
  879. if self._edges.size - 1 != self._values.size:
  880. raise ValueError('Size mismatch between "values" and "edges". '
  881. "Expected `len(values) + 1 == len(edges)`, but "
  882. f"`len(values) = {self._values.size}` and "
  883. f"`len(edges) = {self._edges.size}`.")
  884. # Initializing with empty arrays allows supporting empty stairs.
  885. verts, codes = [np.empty((0, 2))], [np.empty(0, dtype=Path.code_type)]
  886. _nan_mask = np.isnan(self._values)
  887. if self._baseline is not None:
  888. _nan_mask |= np.isnan(self._baseline)
  889. for idx0, idx1 in cbook.contiguous_regions(~_nan_mask):
  890. x = np.repeat(self._edges[idx0:idx1+1], 2)
  891. y = np.repeat(self._values[idx0:idx1], 2)
  892. if self._baseline is None:
  893. y = np.concatenate([y[:1], y, y[-1:]])
  894. elif self._baseline.ndim == 0: # single baseline value
  895. y = np.concatenate([[self._baseline], y, [self._baseline]])
  896. elif self._baseline.ndim == 1: # baseline array
  897. base = np.repeat(self._baseline[idx0:idx1], 2)[::-1]
  898. x = np.concatenate([x, x[::-1]])
  899. y = np.concatenate([base[-1:], y, base[:1],
  900. base[:1], base, base[-1:]])
  901. else: # no baseline
  902. raise ValueError('Invalid `baseline` specified')
  903. if self.orientation == 'vertical':
  904. xy = np.column_stack([x, y])
  905. else:
  906. xy = np.column_stack([y, x])
  907. verts.append(xy)
  908. codes.append([Path.MOVETO] + [Path.LINETO]*(len(xy)-1))
  909. self._path = Path(np.concatenate(verts), np.concatenate(codes))
  910. def get_data(self):
  911. """Get `.StepPatch` values, edges and baseline as namedtuple."""
  912. StairData = namedtuple('StairData', 'values edges baseline')
  913. return StairData(self._values, self._edges, self._baseline)
  914. def set_data(self, values=None, edges=None, baseline=None):
  915. """
  916. Set `.StepPatch` values, edges and baseline.
  917. Parameters
  918. ----------
  919. values : 1D array-like or None
  920. Will not update values, if passing None
  921. edges : 1D array-like, optional
  922. baseline : float, 1D array-like or None
  923. """
  924. if values is None and edges is None and baseline is None:
  925. raise ValueError("Must set *values*, *edges* or *baseline*.")
  926. if values is not None:
  927. self._values = np.asarray(values)
  928. if edges is not None:
  929. self._edges = np.asarray(edges)
  930. if baseline is not None:
  931. self._baseline = np.asarray(baseline)
  932. self._update_path()
  933. self.stale = True
  934. class Polygon(Patch):
  935. """A general polygon patch."""
  936. def __str__(self):
  937. if len(self._path.vertices):
  938. s = "Polygon%d((%g, %g) ...)"
  939. return s % (len(self._path.vertices), *self._path.vertices[0])
  940. else:
  941. return "Polygon0()"
  942. @_docstring.interpd
  943. def __init__(self, xy, *, closed=True, **kwargs):
  944. """
  945. Parameters
  946. ----------
  947. xy : (N, 2) array
  948. closed : bool, default: True
  949. Whether the polygon is closed (i.e., has identical start and end
  950. points).
  951. **kwargs
  952. %(Patch:kwdoc)s
  953. """
  954. super().__init__(**kwargs)
  955. self._closed = closed
  956. self.set_xy(xy)
  957. def get_path(self):
  958. """Get the `.Path` of the polygon."""
  959. return self._path
  960. def get_closed(self):
  961. """Return whether the polygon is closed."""
  962. return self._closed
  963. def set_closed(self, closed):
  964. """
  965. Set whether the polygon is closed.
  966. Parameters
  967. ----------
  968. closed : bool
  969. True if the polygon is closed
  970. """
  971. if self._closed == bool(closed):
  972. return
  973. self._closed = bool(closed)
  974. self.set_xy(self.get_xy())
  975. self.stale = True
  976. def get_xy(self):
  977. """
  978. Get the vertices of the path.
  979. Returns
  980. -------
  981. (N, 2) array
  982. The coordinates of the vertices.
  983. """
  984. return self._path.vertices
  985. def set_xy(self, xy):
  986. """
  987. Set the vertices of the polygon.
  988. Parameters
  989. ----------
  990. xy : (N, 2) array-like
  991. The coordinates of the vertices.
  992. Notes
  993. -----
  994. Unlike `.Path`, we do not ignore the last input vertex. If the
  995. polygon is meant to be closed, and the last point of the polygon is not
  996. equal to the first, we assume that the user has not explicitly passed a
  997. ``CLOSEPOLY`` vertex, and add it ourselves.
  998. """
  999. xy = np.asarray(xy)
  1000. nverts, _ = xy.shape
  1001. if self._closed:
  1002. # if the first and last vertex are the "same", then we assume that
  1003. # the user explicitly passed the CLOSEPOLY vertex. Otherwise, we
  1004. # have to append one since the last vertex will be "ignored" by
  1005. # Path
  1006. if nverts == 1 or nverts > 1 and (xy[0] != xy[-1]).any():
  1007. xy = np.concatenate([xy, [xy[0]]])
  1008. else:
  1009. # if we aren't closed, and the last vertex matches the first, then
  1010. # we assume we have an unnecessary CLOSEPOLY vertex and remove it
  1011. if nverts > 2 and (xy[0] == xy[-1]).all():
  1012. xy = xy[:-1]
  1013. self._path = Path(xy, closed=self._closed)
  1014. self.stale = True
  1015. xy = property(get_xy, set_xy,
  1016. doc='The vertices of the path as a (N, 2) array.')
  1017. class Wedge(Patch):
  1018. """Wedge shaped patch."""
  1019. def __str__(self):
  1020. pars = (self.center[0], self.center[1], self.r,
  1021. self.theta1, self.theta2, self.width)
  1022. fmt = "Wedge(center=(%g, %g), r=%g, theta1=%g, theta2=%g, width=%s)"
  1023. return fmt % pars
  1024. @_docstring.interpd
  1025. def __init__(self, center, r, theta1, theta2, *, width=None, **kwargs):
  1026. """
  1027. A wedge centered at *x*, *y* center with radius *r* that
  1028. sweeps *theta1* to *theta2* (in degrees). If *width* is given,
  1029. then a partial wedge is drawn from inner radius *r* - *width*
  1030. to outer radius *r*.
  1031. Valid keyword arguments are:
  1032. %(Patch:kwdoc)s
  1033. """
  1034. super().__init__(**kwargs)
  1035. self.center = center
  1036. self.r, self.width = r, width
  1037. self.theta1, self.theta2 = theta1, theta2
  1038. self._patch_transform = transforms.IdentityTransform()
  1039. self._recompute_path()
  1040. def _recompute_path(self):
  1041. # Inner and outer rings are connected unless the annulus is complete
  1042. if abs((self.theta2 - self.theta1) - 360) <= 1e-12:
  1043. theta1, theta2 = 0, 360
  1044. connector = Path.MOVETO
  1045. else:
  1046. theta1, theta2 = self.theta1, self.theta2
  1047. connector = Path.LINETO
  1048. # Form the outer ring
  1049. arc = Path.arc(theta1, theta2)
  1050. if self.width is not None:
  1051. # Partial annulus needs to draw the outer ring
  1052. # followed by a reversed and scaled inner ring
  1053. v1 = arc.vertices
  1054. v2 = arc.vertices[::-1] * (self.r - self.width) / self.r
  1055. v = np.concatenate([v1, v2, [(0, 0)]])
  1056. c = [*arc.codes, connector, *arc.codes[1:], Path.CLOSEPOLY]
  1057. else:
  1058. # Wedge doesn't need an inner ring
  1059. v = np.concatenate([arc.vertices, [(0, 0), (0, 0)]])
  1060. c = [*arc.codes, connector, Path.CLOSEPOLY]
  1061. # Shift and scale the wedge to the final location.
  1062. self._path = Path(v * self.r + self.center, c)
  1063. def set_center(self, center):
  1064. self._path = None
  1065. self.center = center
  1066. self.stale = True
  1067. def set_radius(self, radius):
  1068. self._path = None
  1069. self.r = radius
  1070. self.stale = True
  1071. def set_theta1(self, theta1):
  1072. self._path = None
  1073. self.theta1 = theta1
  1074. self.stale = True
  1075. def set_theta2(self, theta2):
  1076. self._path = None
  1077. self.theta2 = theta2
  1078. self.stale = True
  1079. def set_width(self, width):
  1080. self._path = None
  1081. self.width = width
  1082. self.stale = True
  1083. def get_path(self):
  1084. if self._path is None:
  1085. self._recompute_path()
  1086. return self._path
  1087. # COVERAGE NOTE: Not used internally or from examples
  1088. class Arrow(Patch):
  1089. """An arrow patch."""
  1090. def __str__(self):
  1091. return "Arrow()"
  1092. _path = Path._create_closed([
  1093. [0.0, 0.1], [0.0, -0.1], [0.8, -0.1], [0.8, -0.3], [1.0, 0.0],
  1094. [0.8, 0.3], [0.8, 0.1]])
  1095. @_docstring.interpd
  1096. def __init__(self, x, y, dx, dy, *, width=1.0, **kwargs):
  1097. """
  1098. Draws an arrow from (*x*, *y*) to (*x* + *dx*, *y* + *dy*).
  1099. The width of the arrow is scaled by *width*.
  1100. Parameters
  1101. ----------
  1102. x : float
  1103. x coordinate of the arrow tail.
  1104. y : float
  1105. y coordinate of the arrow tail.
  1106. dx : float
  1107. Arrow length in the x direction.
  1108. dy : float
  1109. Arrow length in the y direction.
  1110. width : float, default: 1
  1111. Scale factor for the width of the arrow. With a default value of 1,
  1112. the tail width is 0.2 and head width is 0.6.
  1113. **kwargs
  1114. Keyword arguments control the `Patch` properties:
  1115. %(Patch:kwdoc)s
  1116. See Also
  1117. --------
  1118. FancyArrow
  1119. Patch that allows independent control of the head and tail
  1120. properties.
  1121. """
  1122. super().__init__(**kwargs)
  1123. self.set_data(x, y, dx, dy, width)
  1124. def get_path(self):
  1125. return self._path
  1126. def get_patch_transform(self):
  1127. return self._patch_transform
  1128. def set_data(self, x=None, y=None, dx=None, dy=None, width=None):
  1129. """
  1130. Set `.Arrow` x, y, dx, dy and width.
  1131. Values left as None will not be updated.
  1132. Parameters
  1133. ----------
  1134. x, y : float or None, default: None
  1135. The x and y coordinates of the arrow base.
  1136. dx, dy : float or None, default: None
  1137. The length of the arrow along x and y direction.
  1138. width : float or None, default: None
  1139. Width of full arrow tail.
  1140. """
  1141. if x is not None:
  1142. self._x = x
  1143. if y is not None:
  1144. self._y = y
  1145. if dx is not None:
  1146. self._dx = dx
  1147. if dy is not None:
  1148. self._dy = dy
  1149. if width is not None:
  1150. self._width = width
  1151. self._patch_transform = (
  1152. transforms.Affine2D()
  1153. .scale(np.hypot(self._dx, self._dy), self._width)
  1154. .rotate(np.arctan2(self._dy, self._dx))
  1155. .translate(self._x, self._y)
  1156. .frozen())
  1157. class FancyArrow(Polygon):
  1158. """
  1159. Like Arrow, but lets you set head width and head height independently.
  1160. """
  1161. _edge_default = True
  1162. def __str__(self):
  1163. return "FancyArrow()"
  1164. @_docstring.interpd
  1165. def __init__(self, x, y, dx, dy, *,
  1166. width=0.001, length_includes_head=False, head_width=None,
  1167. head_length=None, shape='full', overhang=0,
  1168. head_starts_at_zero=False, **kwargs):
  1169. """
  1170. Parameters
  1171. ----------
  1172. x, y : float
  1173. The x and y coordinates of the arrow base.
  1174. dx, dy : float
  1175. The length of the arrow along x and y direction.
  1176. width : float, default: 0.001
  1177. Width of full arrow tail.
  1178. length_includes_head : bool, default: False
  1179. True if head is to be counted in calculating the length.
  1180. head_width : float or None, default: 3*width
  1181. Total width of the full arrow head.
  1182. head_length : float or None, default: 1.5*head_width
  1183. Length of arrow head.
  1184. shape : {'full', 'left', 'right'}, default: 'full'
  1185. Draw the left-half, right-half, or full arrow.
  1186. overhang : float, default: 0
  1187. Fraction that the arrow is swept back (0 overhang means
  1188. triangular shape). Can be negative or greater than one.
  1189. head_starts_at_zero : bool, default: False
  1190. If True, the head starts being drawn at coordinate 0
  1191. instead of ending at coordinate 0.
  1192. **kwargs
  1193. `.Patch` properties:
  1194. %(Patch:kwdoc)s
  1195. """
  1196. self._x = x
  1197. self._y = y
  1198. self._dx = dx
  1199. self._dy = dy
  1200. self._width = width
  1201. self._length_includes_head = length_includes_head
  1202. self._head_width = head_width
  1203. self._head_length = head_length
  1204. self._shape = shape
  1205. self._overhang = overhang
  1206. self._head_starts_at_zero = head_starts_at_zero
  1207. self._make_verts()
  1208. super().__init__(self.verts, closed=True, **kwargs)
  1209. def set_data(self, *, x=None, y=None, dx=None, dy=None, width=None,
  1210. head_width=None, head_length=None):
  1211. """
  1212. Set `.FancyArrow` x, y, dx, dy, width, head_with, and head_length.
  1213. Values left as None will not be updated.
  1214. Parameters
  1215. ----------
  1216. x, y : float or None, default: None
  1217. The x and y coordinates of the arrow base.
  1218. dx, dy : float or None, default: None
  1219. The length of the arrow along x and y direction.
  1220. width : float or None, default: None
  1221. Width of full arrow tail.
  1222. head_width : float or None, default: None
  1223. Total width of the full arrow head.
  1224. head_length : float or None, default: None
  1225. Length of arrow head.
  1226. """
  1227. if x is not None:
  1228. self._x = x
  1229. if y is not None:
  1230. self._y = y
  1231. if dx is not None:
  1232. self._dx = dx
  1233. if dy is not None:
  1234. self._dy = dy
  1235. if width is not None:
  1236. self._width = width
  1237. if head_width is not None:
  1238. self._head_width = head_width
  1239. if head_length is not None:
  1240. self._head_length = head_length
  1241. self._make_verts()
  1242. self.set_xy(self.verts)
  1243. def _make_verts(self):
  1244. if self._head_width is None:
  1245. head_width = 3 * self._width
  1246. else:
  1247. head_width = self._head_width
  1248. if self._head_length is None:
  1249. head_length = 1.5 * head_width
  1250. else:
  1251. head_length = self._head_length
  1252. distance = np.hypot(self._dx, self._dy)
  1253. if self._length_includes_head:
  1254. length = distance
  1255. else:
  1256. length = distance + head_length
  1257. if np.size(length) == 0:
  1258. self.verts = np.empty([0, 2]) # display nothing if empty
  1259. else:
  1260. # start by drawing horizontal arrow, point at (0, 0)
  1261. hw, hl = head_width, head_length
  1262. hs, lw = self._overhang, self._width
  1263. left_half_arrow = np.array([
  1264. [0.0, 0.0], # tip
  1265. [-hl, -hw / 2], # leftmost
  1266. [-hl * (1 - hs), -lw / 2], # meets stem
  1267. [-length, -lw / 2], # bottom left
  1268. [-length, 0],
  1269. ])
  1270. # if we're not including the head, shift up by head length
  1271. if not self._length_includes_head:
  1272. left_half_arrow += [head_length, 0]
  1273. # if the head starts at 0, shift up by another head length
  1274. if self._head_starts_at_zero:
  1275. left_half_arrow += [head_length / 2, 0]
  1276. # figure out the shape, and complete accordingly
  1277. if self._shape == 'left':
  1278. coords = left_half_arrow
  1279. else:
  1280. right_half_arrow = left_half_arrow * [1, -1]
  1281. if self._shape == 'right':
  1282. coords = right_half_arrow
  1283. elif self._shape == 'full':
  1284. # The half-arrows contain the midpoint of the stem,
  1285. # which we can omit from the full arrow. Including it
  1286. # twice caused a problem with xpdf.
  1287. coords = np.concatenate([left_half_arrow[:-1],
  1288. right_half_arrow[-2::-1]])
  1289. else:
  1290. raise ValueError(f"Got unknown shape: {self._shape!r}")
  1291. if distance != 0:
  1292. cx = self._dx / distance
  1293. sx = self._dy / distance
  1294. else:
  1295. # Account for division by zero
  1296. cx, sx = 0, 1
  1297. M = [[cx, sx], [-sx, cx]]
  1298. self.verts = np.dot(coords, M) + [
  1299. self._x + self._dx,
  1300. self._y + self._dy,
  1301. ]
  1302. _docstring.interpd.register(
  1303. FancyArrow="\n".join(
  1304. (inspect.getdoc(FancyArrow.__init__) or "").splitlines()[2:]))
  1305. class CirclePolygon(RegularPolygon):
  1306. """A polygon-approximation of a circle patch."""
  1307. def __str__(self):
  1308. s = "CirclePolygon((%g, %g), radius=%g, resolution=%d)"
  1309. return s % (self.xy[0], self.xy[1], self.radius, self.numvertices)
  1310. @_docstring.interpd
  1311. def __init__(self, xy, radius=5, *,
  1312. resolution=20, # the number of vertices
  1313. ** kwargs):
  1314. """
  1315. Create a circle at *xy* = (*x*, *y*) with given *radius*.
  1316. This circle is approximated by a regular polygon with *resolution*
  1317. sides. For a smoother circle drawn with splines, see `Circle`.
  1318. Valid keyword arguments are:
  1319. %(Patch:kwdoc)s
  1320. """
  1321. super().__init__(
  1322. xy, resolution, radius=radius, orientation=0, **kwargs)
  1323. class Ellipse(Patch):
  1324. """A scale-free ellipse."""
  1325. def __str__(self):
  1326. pars = (self._center[0], self._center[1],
  1327. self.width, self.height, self.angle)
  1328. fmt = "Ellipse(xy=(%s, %s), width=%s, height=%s, angle=%s)"
  1329. return fmt % pars
  1330. @_docstring.interpd
  1331. def __init__(self, xy, width, height, *, angle=0, **kwargs):
  1332. """
  1333. Parameters
  1334. ----------
  1335. xy : (float, float)
  1336. xy coordinates of ellipse centre.
  1337. width : float
  1338. Total length (diameter) of horizontal axis.
  1339. height : float
  1340. Total length (diameter) of vertical axis.
  1341. angle : float, default: 0
  1342. Rotation in degrees anti-clockwise.
  1343. Notes
  1344. -----
  1345. Valid keyword arguments are:
  1346. %(Patch:kwdoc)s
  1347. """
  1348. super().__init__(**kwargs)
  1349. self._center = xy
  1350. self._width, self._height = width, height
  1351. self._angle = angle
  1352. self._path = Path.unit_circle()
  1353. # Required for EllipseSelector with axes aspect ratio != 1
  1354. # The patch is defined in data coordinates and when changing the
  1355. # selector with square modifier and not in data coordinates, we need
  1356. # to correct for the aspect ratio difference between the data and
  1357. # display coordinate systems.
  1358. self._aspect_ratio_correction = 1.0
  1359. # Note: This cannot be calculated until this is added to an Axes
  1360. self._patch_transform = transforms.IdentityTransform()
  1361. def _recompute_transform(self):
  1362. """
  1363. Notes
  1364. -----
  1365. This cannot be called until after this has been added to an Axes,
  1366. otherwise unit conversion will fail. This makes it very important to
  1367. call the accessor method and not directly access the transformation
  1368. member variable.
  1369. """
  1370. center = (self.convert_xunits(self._center[0]),
  1371. self.convert_yunits(self._center[1]))
  1372. width = self.convert_xunits(self._width)
  1373. height = self.convert_yunits(self._height)
  1374. self._patch_transform = transforms.Affine2D() \
  1375. .scale(width * 0.5, height * 0.5 * self._aspect_ratio_correction) \
  1376. .rotate_deg(self.angle) \
  1377. .scale(1, 1 / self._aspect_ratio_correction) \
  1378. .translate(*center)
  1379. def get_path(self):
  1380. """Return the path of the ellipse."""
  1381. return self._path
  1382. def get_patch_transform(self):
  1383. self._recompute_transform()
  1384. return self._patch_transform
  1385. def set_center(self, xy):
  1386. """
  1387. Set the center of the ellipse.
  1388. Parameters
  1389. ----------
  1390. xy : (float, float)
  1391. """
  1392. self._center = xy
  1393. self.stale = True
  1394. def get_center(self):
  1395. """Return the center of the ellipse."""
  1396. return self._center
  1397. center = property(get_center, set_center)
  1398. def set_width(self, width):
  1399. """
  1400. Set the width of the ellipse.
  1401. Parameters
  1402. ----------
  1403. width : float
  1404. """
  1405. self._width = width
  1406. self.stale = True
  1407. def get_width(self):
  1408. """
  1409. Return the width of the ellipse.
  1410. """
  1411. return self._width
  1412. width = property(get_width, set_width)
  1413. def set_height(self, height):
  1414. """
  1415. Set the height of the ellipse.
  1416. Parameters
  1417. ----------
  1418. height : float
  1419. """
  1420. self._height = height
  1421. self.stale = True
  1422. def get_height(self):
  1423. """Return the height of the ellipse."""
  1424. return self._height
  1425. height = property(get_height, set_height)
  1426. def set_angle(self, angle):
  1427. """
  1428. Set the angle of the ellipse.
  1429. Parameters
  1430. ----------
  1431. angle : float
  1432. """
  1433. self._angle = angle
  1434. self.stale = True
  1435. def get_angle(self):
  1436. """Return the angle of the ellipse."""
  1437. return self._angle
  1438. angle = property(get_angle, set_angle)
  1439. def get_corners(self):
  1440. """
  1441. Return the corners of the ellipse bounding box.
  1442. The bounding box orientation is moving anti-clockwise from the
  1443. lower left corner defined before rotation.
  1444. """
  1445. return self.get_patch_transform().transform(
  1446. [(-1, -1), (1, -1), (1, 1), (-1, 1)])
  1447. def get_vertices(self):
  1448. """
  1449. Return the vertices coordinates of the ellipse.
  1450. The definition can be found `here <https://en.wikipedia.org/wiki/Ellipse>`_
  1451. .. versionadded:: 3.8
  1452. """
  1453. if self.width < self.height:
  1454. ret = self.get_patch_transform().transform([(0, 1), (0, -1)])
  1455. else:
  1456. ret = self.get_patch_transform().transform([(1, 0), (-1, 0)])
  1457. return [tuple(x) for x in ret]
  1458. def get_co_vertices(self):
  1459. """
  1460. Return the co-vertices coordinates of the ellipse.
  1461. The definition can be found `here <https://en.wikipedia.org/wiki/Ellipse>`_
  1462. .. versionadded:: 3.8
  1463. """
  1464. if self.width < self.height:
  1465. ret = self.get_patch_transform().transform([(1, 0), (-1, 0)])
  1466. else:
  1467. ret = self.get_patch_transform().transform([(0, 1), (0, -1)])
  1468. return [tuple(x) for x in ret]
  1469. class Annulus(Patch):
  1470. """
  1471. An elliptical annulus.
  1472. """
  1473. @_docstring.interpd
  1474. def __init__(self, xy, r, width, angle=0.0, **kwargs):
  1475. """
  1476. Parameters
  1477. ----------
  1478. xy : (float, float)
  1479. xy coordinates of annulus centre.
  1480. r : float or (float, float)
  1481. The radius, or semi-axes:
  1482. - If float: radius of the outer circle.
  1483. - If two floats: semi-major and -minor axes of outer ellipse.
  1484. width : float
  1485. Width (thickness) of the annular ring. The width is measured inward
  1486. from the outer ellipse so that for the inner ellipse the semi-axes
  1487. are given by ``r - width``. *width* must be less than or equal to
  1488. the semi-minor axis.
  1489. angle : float, default: 0
  1490. Rotation angle in degrees (anti-clockwise from the positive
  1491. x-axis). Ignored for circular annuli (i.e., if *r* is a scalar).
  1492. **kwargs
  1493. Keyword arguments control the `Patch` properties:
  1494. %(Patch:kwdoc)s
  1495. """
  1496. super().__init__(**kwargs)
  1497. self.set_radii(r)
  1498. self.center = xy
  1499. self.width = width
  1500. self.angle = angle
  1501. self._path = None
  1502. def __str__(self):
  1503. if self.a == self.b:
  1504. r = self.a
  1505. else:
  1506. r = (self.a, self.b)
  1507. return "Annulus(xy=(%s, %s), r=%s, width=%s, angle=%s)" % \
  1508. (*self.center, r, self.width, self.angle)
  1509. def set_center(self, xy):
  1510. """
  1511. Set the center of the annulus.
  1512. Parameters
  1513. ----------
  1514. xy : (float, float)
  1515. """
  1516. self._center = xy
  1517. self._path = None
  1518. self.stale = True
  1519. def get_center(self):
  1520. """Return the center of the annulus."""
  1521. return self._center
  1522. center = property(get_center, set_center)
  1523. def set_width(self, width):
  1524. """
  1525. Set the width (thickness) of the annulus ring.
  1526. The width is measured inwards from the outer ellipse.
  1527. Parameters
  1528. ----------
  1529. width : float
  1530. """
  1531. if width > min(self.a, self.b):
  1532. raise ValueError(
  1533. 'Width of annulus must be less than or equal to semi-minor axis')
  1534. self._width = width
  1535. self._path = None
  1536. self.stale = True
  1537. def get_width(self):
  1538. """Return the width (thickness) of the annulus ring."""
  1539. return self._width
  1540. width = property(get_width, set_width)
  1541. def set_angle(self, angle):
  1542. """
  1543. Set the tilt angle of the annulus.
  1544. Parameters
  1545. ----------
  1546. angle : float
  1547. """
  1548. self._angle = angle
  1549. self._path = None
  1550. self.stale = True
  1551. def get_angle(self):
  1552. """Return the angle of the annulus."""
  1553. return self._angle
  1554. angle = property(get_angle, set_angle)
  1555. def set_semimajor(self, a):
  1556. """
  1557. Set the semi-major axis *a* of the annulus.
  1558. Parameters
  1559. ----------
  1560. a : float
  1561. """
  1562. self.a = float(a)
  1563. self._path = None
  1564. self.stale = True
  1565. def set_semiminor(self, b):
  1566. """
  1567. Set the semi-minor axis *b* of the annulus.
  1568. Parameters
  1569. ----------
  1570. b : float
  1571. """
  1572. self.b = float(b)
  1573. self._path = None
  1574. self.stale = True
  1575. def set_radii(self, r):
  1576. """
  1577. Set the semi-major (*a*) and semi-minor radii (*b*) of the annulus.
  1578. Parameters
  1579. ----------
  1580. r : float or (float, float)
  1581. The radius, or semi-axes:
  1582. - If float: radius of the outer circle.
  1583. - If two floats: semi-major and -minor axes of outer ellipse.
  1584. """
  1585. if np.shape(r) == (2,):
  1586. self.a, self.b = r
  1587. elif np.shape(r) == ():
  1588. self.a = self.b = float(r)
  1589. else:
  1590. raise ValueError("Parameter 'r' must be one or two floats.")
  1591. self._path = None
  1592. self.stale = True
  1593. def get_radii(self):
  1594. """Return the semi-major and semi-minor radii of the annulus."""
  1595. return self.a, self.b
  1596. radii = property(get_radii, set_radii)
  1597. def _transform_verts(self, verts, a, b):
  1598. return transforms.Affine2D() \
  1599. .scale(*self._convert_xy_units((a, b))) \
  1600. .rotate_deg(self.angle) \
  1601. .translate(*self._convert_xy_units(self.center)) \
  1602. .transform(verts)
  1603. def _recompute_path(self):
  1604. # circular arc
  1605. arc = Path.arc(0, 360)
  1606. # annulus needs to draw an outer ring
  1607. # followed by a reversed and scaled inner ring
  1608. a, b, w = self.a, self.b, self.width
  1609. v1 = self._transform_verts(arc.vertices, a, b)
  1610. v2 = self._transform_verts(arc.vertices[::-1], a - w, b - w)
  1611. v = np.vstack([v1, v2, v1[0, :], (0, 0)])
  1612. c = np.hstack([arc.codes, Path.MOVETO,
  1613. arc.codes[1:], Path.MOVETO,
  1614. Path.CLOSEPOLY])
  1615. self._path = Path(v, c)
  1616. def get_path(self):
  1617. if self._path is None:
  1618. self._recompute_path()
  1619. return self._path
  1620. class Circle(Ellipse):
  1621. """
  1622. A circle patch.
  1623. """
  1624. def __str__(self):
  1625. pars = self.center[0], self.center[1], self.radius
  1626. fmt = "Circle(xy=(%g, %g), radius=%g)"
  1627. return fmt % pars
  1628. @_docstring.interpd
  1629. def __init__(self, xy, radius=5, **kwargs):
  1630. """
  1631. Create a true circle at center *xy* = (*x*, *y*) with given *radius*.
  1632. Unlike `CirclePolygon` which is a polygonal approximation, this uses
  1633. Bezier splines and is much closer to a scale-free circle.
  1634. Valid keyword arguments are:
  1635. %(Patch:kwdoc)s
  1636. """
  1637. super().__init__(xy, radius * 2, radius * 2, **kwargs)
  1638. self.radius = radius
  1639. def set_radius(self, radius):
  1640. """
  1641. Set the radius of the circle.
  1642. Parameters
  1643. ----------
  1644. radius : float
  1645. """
  1646. self.width = self.height = 2 * radius
  1647. self.stale = True
  1648. def get_radius(self):
  1649. """Return the radius of the circle."""
  1650. return self.width / 2.
  1651. radius = property(get_radius, set_radius)
  1652. class Arc(Ellipse):
  1653. """
  1654. An elliptical arc, i.e. a segment of an ellipse.
  1655. Due to internal optimizations, the arc cannot be filled.
  1656. """
  1657. def __str__(self):
  1658. pars = (self.center[0], self.center[1], self.width,
  1659. self.height, self.angle, self.theta1, self.theta2)
  1660. fmt = ("Arc(xy=(%g, %g), width=%g, "
  1661. "height=%g, angle=%g, theta1=%g, theta2=%g)")
  1662. return fmt % pars
  1663. @_docstring.interpd
  1664. def __init__(self, xy, width, height, *,
  1665. angle=0.0, theta1=0.0, theta2=360.0, **kwargs):
  1666. """
  1667. Parameters
  1668. ----------
  1669. xy : (float, float)
  1670. The center of the ellipse.
  1671. width : float
  1672. The length of the horizontal axis.
  1673. height : float
  1674. The length of the vertical axis.
  1675. angle : float
  1676. Rotation of the ellipse in degrees (counterclockwise).
  1677. theta1, theta2 : float, default: 0, 360
  1678. Starting and ending angles of the arc in degrees. These values
  1679. are relative to *angle*, e.g. if *angle* = 45 and *theta1* = 90
  1680. the absolute starting angle is 135.
  1681. Default *theta1* = 0, *theta2* = 360, i.e. a complete ellipse.
  1682. The arc is drawn in the counterclockwise direction.
  1683. Angles greater than or equal to 360, or smaller than 0, are
  1684. represented by an equivalent angle in the range [0, 360), by
  1685. taking the input value mod 360.
  1686. Other Parameters
  1687. ----------------
  1688. **kwargs : `~matplotlib.patches.Patch` properties
  1689. Most `.Patch` properties are supported as keyword arguments,
  1690. except *fill* and *facecolor* because filling is not supported.
  1691. %(Patch:kwdoc)s
  1692. """
  1693. fill = kwargs.setdefault('fill', False)
  1694. if fill:
  1695. raise ValueError("Arc objects cannot be filled")
  1696. super().__init__(xy, width, height, angle=angle, **kwargs)
  1697. self.theta1 = theta1
  1698. self.theta2 = theta2
  1699. (self._theta1, self._theta2, self._stretched_width,
  1700. self._stretched_height) = self._theta_stretch()
  1701. self._path = Path.arc(self._theta1, self._theta2)
  1702. @artist.allow_rasterization
  1703. def draw(self, renderer):
  1704. """
  1705. Draw the arc to the given *renderer*.
  1706. Notes
  1707. -----
  1708. Ellipses are normally drawn using an approximation that uses
  1709. eight cubic Bezier splines. The error of this approximation
  1710. is 1.89818e-6, according to this unverified source:
  1711. Lancaster, Don. *Approximating a Circle or an Ellipse Using
  1712. Four Bezier Cubic Splines.*
  1713. https://www.tinaja.com/glib/ellipse4.pdf
  1714. There is a use case where very large ellipses must be drawn
  1715. with very high accuracy, and it is too expensive to render the
  1716. entire ellipse with enough segments (either splines or line
  1717. segments). Therefore, in the case where either radius of the
  1718. ellipse is large enough that the error of the spline
  1719. approximation will be visible (greater than one pixel offset
  1720. from the ideal), a different technique is used.
  1721. In that case, only the visible parts of the ellipse are drawn,
  1722. with each visible arc using a fixed number of spline segments
  1723. (8). The algorithm proceeds as follows:
  1724. 1. The points where the ellipse intersects the axes (or figure)
  1725. bounding box are located. (This is done by performing an inverse
  1726. transformation on the bbox such that it is relative to the unit
  1727. circle -- this makes the intersection calculation much easier than
  1728. doing rotated ellipse intersection directly.)
  1729. This uses the "line intersecting a circle" algorithm from:
  1730. Vince, John. *Geometry for Computer Graphics: Formulae,
  1731. Examples & Proofs.* London: Springer-Verlag, 2005.
  1732. 2. The angles of each of the intersection points are calculated.
  1733. 3. Proceeding counterclockwise starting in the positive
  1734. x-direction, each of the visible arc-segments between the
  1735. pairs of vertices are drawn using the Bezier arc
  1736. approximation technique implemented in `.Path.arc`.
  1737. """
  1738. if not self.get_visible():
  1739. return
  1740. self._recompute_transform()
  1741. self._update_path()
  1742. # Get width and height in pixels we need to use
  1743. # `self.get_data_transform` rather than `self.get_transform`
  1744. # because we want the transform from dataspace to the
  1745. # screen space to estimate how big the arc will be in physical
  1746. # units when rendered (the transform that we get via
  1747. # `self.get_transform()` goes from an idealized unit-radius
  1748. # space to screen space).
  1749. data_to_screen_trans = self.get_data_transform()
  1750. pwidth, pheight = (
  1751. data_to_screen_trans.transform((self._stretched_width,
  1752. self._stretched_height)) -
  1753. data_to_screen_trans.transform((0, 0)))
  1754. inv_error = (1.0 / 1.89818e-6) * 0.5
  1755. if pwidth < inv_error and pheight < inv_error:
  1756. return Patch.draw(self, renderer)
  1757. def line_circle_intersect(x0, y0, x1, y1):
  1758. dx = x1 - x0
  1759. dy = y1 - y0
  1760. dr2 = dx * dx + dy * dy
  1761. D = x0 * y1 - x1 * y0
  1762. D2 = D * D
  1763. discrim = dr2 - D2
  1764. if discrim >= 0.0:
  1765. sign_dy = np.copysign(1, dy) # +/-1, never 0.
  1766. sqrt_discrim = np.sqrt(discrim)
  1767. return np.array(
  1768. [[(D * dy + sign_dy * dx * sqrt_discrim) / dr2,
  1769. (-D * dx + abs(dy) * sqrt_discrim) / dr2],
  1770. [(D * dy - sign_dy * dx * sqrt_discrim) / dr2,
  1771. (-D * dx - abs(dy) * sqrt_discrim) / dr2]])
  1772. else:
  1773. return np.empty((0, 2))
  1774. def segment_circle_intersect(x0, y0, x1, y1):
  1775. epsilon = 1e-9
  1776. if x1 < x0:
  1777. x0e, x1e = x1, x0
  1778. else:
  1779. x0e, x1e = x0, x1
  1780. if y1 < y0:
  1781. y0e, y1e = y1, y0
  1782. else:
  1783. y0e, y1e = y0, y1
  1784. xys = line_circle_intersect(x0, y0, x1, y1)
  1785. xs, ys = xys.T
  1786. return xys[
  1787. (x0e - epsilon < xs) & (xs < x1e + epsilon)
  1788. & (y0e - epsilon < ys) & (ys < y1e + epsilon)
  1789. ]
  1790. # Transform the Axes (or figure) box_path so that it is relative to
  1791. # the unit circle in the same way that it is relative to the desired
  1792. # ellipse.
  1793. box_path_transform = (
  1794. transforms.BboxTransformTo((self.axes or self.get_figure(root=False)).bbox)
  1795. - self.get_transform())
  1796. box_path = Path.unit_rectangle().transformed(box_path_transform)
  1797. thetas = set()
  1798. # For each of the point pairs, there is a line segment
  1799. for p0, p1 in zip(box_path.vertices[:-1], box_path.vertices[1:]):
  1800. xy = segment_circle_intersect(*p0, *p1)
  1801. x, y = xy.T
  1802. # arctan2 return [-pi, pi), the rest of our angles are in
  1803. # [0, 360], adjust as needed.
  1804. theta = (np.rad2deg(np.arctan2(y, x)) + 360) % 360
  1805. thetas.update(
  1806. theta[(self._theta1 < theta) & (theta < self._theta2)])
  1807. thetas = sorted(thetas) + [self._theta2]
  1808. last_theta = self._theta1
  1809. theta1_rad = np.deg2rad(self._theta1)
  1810. inside = box_path.contains_point(
  1811. (np.cos(theta1_rad), np.sin(theta1_rad))
  1812. )
  1813. # save original path
  1814. path_original = self._path
  1815. for theta in thetas:
  1816. if inside:
  1817. self._path = Path.arc(last_theta, theta, 8)
  1818. Patch.draw(self, renderer)
  1819. inside = False
  1820. else:
  1821. inside = True
  1822. last_theta = theta
  1823. # restore original path
  1824. self._path = path_original
  1825. def _update_path(self):
  1826. # Compute new values and update and set new _path if any value changed
  1827. stretched = self._theta_stretch()
  1828. if any(a != b for a, b in zip(
  1829. stretched, (self._theta1, self._theta2, self._stretched_width,
  1830. self._stretched_height))):
  1831. (self._theta1, self._theta2, self._stretched_width,
  1832. self._stretched_height) = stretched
  1833. self._path = Path.arc(self._theta1, self._theta2)
  1834. def _theta_stretch(self):
  1835. # If the width and height of ellipse are not equal, take into account
  1836. # stretching when calculating angles to draw between
  1837. def theta_stretch(theta, scale):
  1838. theta = np.deg2rad(theta)
  1839. x = np.cos(theta)
  1840. y = np.sin(theta)
  1841. stheta = np.rad2deg(np.arctan2(scale * y, x))
  1842. # arctan2 has the range [-pi, pi], we expect [0, 2*pi]
  1843. return (stheta + 360) % 360
  1844. width = self.convert_xunits(self.width)
  1845. height = self.convert_yunits(self.height)
  1846. if (
  1847. # if we need to stretch the angles because we are distorted
  1848. width != height
  1849. # and we are not doing a full circle.
  1850. #
  1851. # 0 and 360 do not exactly round-trip through the angle
  1852. # stretching (due to both float precision limitations and
  1853. # the difference between the range of arctan2 [-pi, pi] and
  1854. # this method [0, 360]) so avoid doing it if we don't have to.
  1855. and not (self.theta1 != self.theta2 and
  1856. self.theta1 % 360 == self.theta2 % 360)
  1857. ):
  1858. theta1 = theta_stretch(self.theta1, width / height)
  1859. theta2 = theta_stretch(self.theta2, width / height)
  1860. return theta1, theta2, width, height
  1861. return self.theta1, self.theta2, width, height
  1862. def bbox_artist(artist, renderer, props=None, fill=True):
  1863. """
  1864. A debug function to draw a rectangle around the bounding
  1865. box returned by an artist's `.Artist.get_window_extent`
  1866. to test whether the artist is returning the correct bbox.
  1867. *props* is a dict of rectangle props with the additional property
  1868. 'pad' that sets the padding around the bbox in points.
  1869. """
  1870. if props is None:
  1871. props = {}
  1872. props = props.copy() # don't want to alter the pad externally
  1873. pad = props.pop('pad', 4)
  1874. pad = renderer.points_to_pixels(pad)
  1875. bbox = artist.get_window_extent(renderer)
  1876. r = Rectangle(
  1877. xy=(bbox.x0 - pad / 2, bbox.y0 - pad / 2),
  1878. width=bbox.width + pad, height=bbox.height + pad,
  1879. fill=fill, transform=transforms.IdentityTransform(), clip_on=False)
  1880. r.update(props)
  1881. r.draw(renderer)
  1882. def draw_bbox(bbox, renderer, color='k', trans=None):
  1883. """
  1884. A debug function to draw a rectangle around the bounding
  1885. box returned by an artist's `.Artist.get_window_extent`
  1886. to test whether the artist is returning the correct bbox.
  1887. """
  1888. r = Rectangle(xy=bbox.p0, width=bbox.width, height=bbox.height,
  1889. edgecolor=color, fill=False, clip_on=False)
  1890. if trans is not None:
  1891. r.set_transform(trans)
  1892. r.draw(renderer)
  1893. class _Style:
  1894. """
  1895. A base class for the Styles. It is meant to be a container class,
  1896. where actual styles are declared as subclass of it, and it
  1897. provides some helper functions.
  1898. """
  1899. def __init_subclass__(cls):
  1900. # Automatically perform docstring interpolation on the subclasses:
  1901. # This allows listing the supported styles via
  1902. # - %(BoxStyle:table)s
  1903. # - %(ConnectionStyle:table)s
  1904. # - %(ArrowStyle:table)s
  1905. # and additionally adding .. ACCEPTS: blocks via
  1906. # - %(BoxStyle:table_and_accepts)s
  1907. # - %(ConnectionStyle:table_and_accepts)s
  1908. # - %(ArrowStyle:table_and_accepts)s
  1909. _docstring.interpd.register(**{
  1910. f"{cls.__name__}:table": cls.pprint_styles(),
  1911. f"{cls.__name__}:table_and_accepts": (
  1912. cls.pprint_styles()
  1913. + "\n\n .. ACCEPTS: ["
  1914. + "|".join(map(" '{}' ".format, cls._style_list))
  1915. + "]")
  1916. })
  1917. def __new__(cls, stylename, **kwargs):
  1918. """Return the instance of the subclass with the given style name."""
  1919. # The "class" should have the _style_list attribute, which is a mapping
  1920. # of style names to style classes.
  1921. _list = stylename.replace(" ", "").split(",")
  1922. _name = _list[0].lower()
  1923. try:
  1924. _cls = cls._style_list[_name]
  1925. except KeyError as err:
  1926. raise ValueError(f"Unknown style: {stylename!r}") from err
  1927. try:
  1928. _args_pair = [cs.split("=") for cs in _list[1:]]
  1929. _args = {k: float(v) for k, v in _args_pair}
  1930. except ValueError as err:
  1931. raise ValueError(
  1932. f"Incorrect style argument: {stylename!r}") from err
  1933. return _cls(**{**_args, **kwargs})
  1934. @classmethod
  1935. def get_styles(cls):
  1936. """Return a dictionary of available styles."""
  1937. return cls._style_list
  1938. @classmethod
  1939. def pprint_styles(cls):
  1940. """Return the available styles as pretty-printed string."""
  1941. table = [('Class', 'Name', 'Parameters'),
  1942. *[(cls.__name__,
  1943. # Add backquotes, as - and | have special meaning in reST.
  1944. f'``{name}``',
  1945. # [1:-1] drops the surrounding parentheses.
  1946. str(inspect.signature(cls))[1:-1] or 'None')
  1947. for name, cls in cls._style_list.items()]]
  1948. # Convert to rst table.
  1949. col_len = [max(len(cell) for cell in column) for column in zip(*table)]
  1950. table_formatstr = ' '.join('=' * cl for cl in col_len)
  1951. rst_table = '\n'.join([
  1952. '',
  1953. table_formatstr,
  1954. ' '.join(cell.ljust(cl) for cell, cl in zip(table[0], col_len)),
  1955. table_formatstr,
  1956. *[' '.join(cell.ljust(cl) for cell, cl in zip(row, col_len))
  1957. for row in table[1:]],
  1958. table_formatstr,
  1959. ])
  1960. return textwrap.indent(rst_table, prefix=' ' * 4)
  1961. @classmethod
  1962. @_api.deprecated(
  1963. '3.10.0',
  1964. message="This method is never used internally.",
  1965. alternative="No replacement. Please open an issue if you use this."
  1966. )
  1967. def register(cls, name, style):
  1968. """Register a new style."""
  1969. if not issubclass(style, cls._Base):
  1970. raise ValueError(f"{style} must be a subclass of {cls._Base}")
  1971. cls._style_list[name] = style
  1972. def _register_style(style_list, cls=None, *, name=None):
  1973. """Class decorator that stashes a class in a (style) dictionary."""
  1974. if cls is None:
  1975. return functools.partial(_register_style, style_list, name=name)
  1976. style_list[name or cls.__name__.lower()] = cls
  1977. return cls
  1978. @_docstring.interpd
  1979. class BoxStyle(_Style):
  1980. """
  1981. `BoxStyle` is a container class which defines several
  1982. boxstyle classes, which are used for `FancyBboxPatch`.
  1983. A style object can be created as::
  1984. BoxStyle.Round(pad=0.2)
  1985. or::
  1986. BoxStyle("Round", pad=0.2)
  1987. or::
  1988. BoxStyle("Round, pad=0.2")
  1989. The following boxstyle classes are defined.
  1990. %(BoxStyle:table)s
  1991. An instance of a boxstyle class is a callable object, with the signature ::
  1992. __call__(self, x0, y0, width, height, mutation_size) -> Path
  1993. *x0*, *y0*, *width* and *height* specify the location and size of the box
  1994. to be drawn; *mutation_size* scales the outline properties such as padding.
  1995. """
  1996. _style_list = {}
  1997. @_register_style(_style_list)
  1998. class Square:
  1999. """A square box."""
  2000. def __init__(self, pad=0.3):
  2001. """
  2002. Parameters
  2003. ----------
  2004. pad : float, default: 0.3
  2005. The amount of padding around the original box.
  2006. """
  2007. self.pad = pad
  2008. def __call__(self, x0, y0, width, height, mutation_size):
  2009. pad = mutation_size * self.pad
  2010. # width and height with padding added.
  2011. width, height = width + 2 * pad, height + 2 * pad
  2012. # boundary of the padded box
  2013. x0, y0 = x0 - pad, y0 - pad
  2014. x1, y1 = x0 + width, y0 + height
  2015. return Path._create_closed(
  2016. [(x0, y0), (x1, y0), (x1, y1), (x0, y1)])
  2017. @_register_style(_style_list)
  2018. class Circle:
  2019. """A circular box."""
  2020. def __init__(self, pad=0.3):
  2021. """
  2022. Parameters
  2023. ----------
  2024. pad : float, default: 0.3
  2025. The amount of padding around the original box.
  2026. """
  2027. self.pad = pad
  2028. def __call__(self, x0, y0, width, height, mutation_size):
  2029. pad = mutation_size * self.pad
  2030. width, height = width + 2 * pad, height + 2 * pad
  2031. # boundary of the padded box
  2032. x0, y0 = x0 - pad, y0 - pad
  2033. return Path.circle((x0 + width / 2, y0 + height / 2),
  2034. max(width, height) / 2)
  2035. @_register_style(_style_list)
  2036. class Ellipse:
  2037. """
  2038. An elliptical box.
  2039. .. versionadded:: 3.7
  2040. """
  2041. def __init__(self, pad=0.3):
  2042. """
  2043. Parameters
  2044. ----------
  2045. pad : float, default: 0.3
  2046. The amount of padding around the original box.
  2047. """
  2048. self.pad = pad
  2049. def __call__(self, x0, y0, width, height, mutation_size):
  2050. pad = mutation_size * self.pad
  2051. width, height = width + 2 * pad, height + 2 * pad
  2052. # boundary of the padded box
  2053. x0, y0 = x0 - pad, y0 - pad
  2054. a = width / math.sqrt(2)
  2055. b = height / math.sqrt(2)
  2056. trans = Affine2D().scale(a, b).translate(x0 + width / 2,
  2057. y0 + height / 2)
  2058. return trans.transform_path(Path.unit_circle())
  2059. @_register_style(_style_list)
  2060. class LArrow:
  2061. """A box in the shape of a left-pointing arrow."""
  2062. def __init__(self, pad=0.3):
  2063. """
  2064. Parameters
  2065. ----------
  2066. pad : float, default: 0.3
  2067. The amount of padding around the original box.
  2068. """
  2069. self.pad = pad
  2070. def __call__(self, x0, y0, width, height, mutation_size):
  2071. # padding
  2072. pad = mutation_size * self.pad
  2073. # width and height with padding added.
  2074. width, height = width + 2 * pad, height + 2 * pad
  2075. # boundary of the padded box
  2076. x0, y0 = x0 - pad, y0 - pad,
  2077. x1, y1 = x0 + width, y0 + height
  2078. dx = (y1 - y0) / 2
  2079. dxx = dx / 2
  2080. x0 = x0 + pad / 1.4 # adjust by ~sqrt(2)
  2081. return Path._create_closed(
  2082. [(x0 + dxx, y0), (x1, y0), (x1, y1), (x0 + dxx, y1),
  2083. (x0 + dxx, y1 + dxx), (x0 - dx, y0 + dx),
  2084. (x0 + dxx, y0 - dxx), # arrow
  2085. (x0 + dxx, y0)])
  2086. @_register_style(_style_list)
  2087. class RArrow(LArrow):
  2088. """A box in the shape of a right-pointing arrow."""
  2089. def __call__(self, x0, y0, width, height, mutation_size):
  2090. p = BoxStyle.LArrow.__call__(
  2091. self, x0, y0, width, height, mutation_size)
  2092. p.vertices[:, 0] = 2 * x0 + width - p.vertices[:, 0]
  2093. return p
  2094. @_register_style(_style_list)
  2095. class DArrow:
  2096. """A box in the shape of a two-way arrow."""
  2097. # Modified from LArrow to add a right arrow to the bbox.
  2098. def __init__(self, pad=0.3):
  2099. """
  2100. Parameters
  2101. ----------
  2102. pad : float, default: 0.3
  2103. The amount of padding around the original box.
  2104. """
  2105. self.pad = pad
  2106. def __call__(self, x0, y0, width, height, mutation_size):
  2107. # padding
  2108. pad = mutation_size * self.pad
  2109. # width and height with padding added.
  2110. # The width is padded by the arrows, so we don't need to pad it.
  2111. height = height + 2 * pad
  2112. # boundary of the padded box
  2113. x0, y0 = x0 - pad, y0 - pad
  2114. x1, y1 = x0 + width, y0 + height
  2115. dx = (y1 - y0) / 2
  2116. dxx = dx / 2
  2117. x0 = x0 + pad / 1.4 # adjust by ~sqrt(2)
  2118. return Path._create_closed([
  2119. (x0 + dxx, y0), (x1, y0), # bot-segment
  2120. (x1, y0 - dxx), (x1 + dx + dxx, y0 + dx),
  2121. (x1, y1 + dxx), # right-arrow
  2122. (x1, y1), (x0 + dxx, y1), # top-segment
  2123. (x0 + dxx, y1 + dxx), (x0 - dx, y0 + dx),
  2124. (x0 + dxx, y0 - dxx), # left-arrow
  2125. (x0 + dxx, y0)])
  2126. @_register_style(_style_list)
  2127. class Round:
  2128. """A box with round corners."""
  2129. def __init__(self, pad=0.3, rounding_size=None):
  2130. """
  2131. Parameters
  2132. ----------
  2133. pad : float, default: 0.3
  2134. The amount of padding around the original box.
  2135. rounding_size : float, default: *pad*
  2136. Radius of the corners.
  2137. """
  2138. self.pad = pad
  2139. self.rounding_size = rounding_size
  2140. def __call__(self, x0, y0, width, height, mutation_size):
  2141. # padding
  2142. pad = mutation_size * self.pad
  2143. # size of the rounding corner
  2144. if self.rounding_size:
  2145. dr = mutation_size * self.rounding_size
  2146. else:
  2147. dr = pad
  2148. width, height = width + 2 * pad, height + 2 * pad
  2149. x0, y0 = x0 - pad, y0 - pad,
  2150. x1, y1 = x0 + width, y0 + height
  2151. # Round corners are implemented as quadratic Bezier, e.g.,
  2152. # [(x0, y0-dr), (x0, y0), (x0+dr, y0)] for lower left corner.
  2153. cp = [(x0 + dr, y0),
  2154. (x1 - dr, y0),
  2155. (x1, y0), (x1, y0 + dr),
  2156. (x1, y1 - dr),
  2157. (x1, y1), (x1 - dr, y1),
  2158. (x0 + dr, y1),
  2159. (x0, y1), (x0, y1 - dr),
  2160. (x0, y0 + dr),
  2161. (x0, y0), (x0 + dr, y0),
  2162. (x0 + dr, y0)]
  2163. com = [Path.MOVETO,
  2164. Path.LINETO,
  2165. Path.CURVE3, Path.CURVE3,
  2166. Path.LINETO,
  2167. Path.CURVE3, Path.CURVE3,
  2168. Path.LINETO,
  2169. Path.CURVE3, Path.CURVE3,
  2170. Path.LINETO,
  2171. Path.CURVE3, Path.CURVE3,
  2172. Path.CLOSEPOLY]
  2173. return Path(cp, com)
  2174. @_register_style(_style_list)
  2175. class Round4:
  2176. """A box with rounded edges."""
  2177. def __init__(self, pad=0.3, rounding_size=None):
  2178. """
  2179. Parameters
  2180. ----------
  2181. pad : float, default: 0.3
  2182. The amount of padding around the original box.
  2183. rounding_size : float, default: *pad*/2
  2184. Rounding of edges.
  2185. """
  2186. self.pad = pad
  2187. self.rounding_size = rounding_size
  2188. def __call__(self, x0, y0, width, height, mutation_size):
  2189. # padding
  2190. pad = mutation_size * self.pad
  2191. # Rounding size; defaults to half of the padding.
  2192. if self.rounding_size:
  2193. dr = mutation_size * self.rounding_size
  2194. else:
  2195. dr = pad / 2.
  2196. width = width + 2 * pad - 2 * dr
  2197. height = height + 2 * pad - 2 * dr
  2198. x0, y0 = x0 - pad + dr, y0 - pad + dr,
  2199. x1, y1 = x0 + width, y0 + height
  2200. cp = [(x0, y0),
  2201. (x0 + dr, y0 - dr), (x1 - dr, y0 - dr), (x1, y0),
  2202. (x1 + dr, y0 + dr), (x1 + dr, y1 - dr), (x1, y1),
  2203. (x1 - dr, y1 + dr), (x0 + dr, y1 + dr), (x0, y1),
  2204. (x0 - dr, y1 - dr), (x0 - dr, y0 + dr), (x0, y0),
  2205. (x0, y0)]
  2206. com = [Path.MOVETO,
  2207. Path.CURVE4, Path.CURVE4, Path.CURVE4,
  2208. Path.CURVE4, Path.CURVE4, Path.CURVE4,
  2209. Path.CURVE4, Path.CURVE4, Path.CURVE4,
  2210. Path.CURVE4, Path.CURVE4, Path.CURVE4,
  2211. Path.CLOSEPOLY]
  2212. return Path(cp, com)
  2213. @_register_style(_style_list)
  2214. class Sawtooth:
  2215. """A box with a sawtooth outline."""
  2216. def __init__(self, pad=0.3, tooth_size=None):
  2217. """
  2218. Parameters
  2219. ----------
  2220. pad : float, default: 0.3
  2221. The amount of padding around the original box.
  2222. tooth_size : float, default: *pad*/2
  2223. Size of the sawtooth.
  2224. """
  2225. self.pad = pad
  2226. self.tooth_size = tooth_size
  2227. def _get_sawtooth_vertices(self, x0, y0, width, height, mutation_size):
  2228. # padding
  2229. pad = mutation_size * self.pad
  2230. # size of sawtooth
  2231. if self.tooth_size is None:
  2232. tooth_size = self.pad * .5 * mutation_size
  2233. else:
  2234. tooth_size = self.tooth_size * mutation_size
  2235. hsz = tooth_size / 2
  2236. width = width + 2 * pad - tooth_size
  2237. height = height + 2 * pad - tooth_size
  2238. # the sizes of the vertical and horizontal sawtooth are
  2239. # separately adjusted to fit the given box size.
  2240. dsx_n = round((width - tooth_size) / (tooth_size * 2)) * 2
  2241. dsy_n = round((height - tooth_size) / (tooth_size * 2)) * 2
  2242. x0, y0 = x0 - pad + hsz, y0 - pad + hsz
  2243. x1, y1 = x0 + width, y0 + height
  2244. xs = [
  2245. x0, *np.linspace(x0 + hsz, x1 - hsz, 2 * dsx_n + 1), # bottom
  2246. *([x1, x1 + hsz, x1, x1 - hsz] * dsy_n)[:2*dsy_n+2], # right
  2247. x1, *np.linspace(x1 - hsz, x0 + hsz, 2 * dsx_n + 1), # top
  2248. *([x0, x0 - hsz, x0, x0 + hsz] * dsy_n)[:2*dsy_n+2], # left
  2249. ]
  2250. ys = [
  2251. *([y0, y0 - hsz, y0, y0 + hsz] * dsx_n)[:2*dsx_n+2], # bottom
  2252. y0, *np.linspace(y0 + hsz, y1 - hsz, 2 * dsy_n + 1), # right
  2253. *([y1, y1 + hsz, y1, y1 - hsz] * dsx_n)[:2*dsx_n+2], # top
  2254. y1, *np.linspace(y1 - hsz, y0 + hsz, 2 * dsy_n + 1), # left
  2255. ]
  2256. return [*zip(xs, ys), (xs[0], ys[0])]
  2257. def __call__(self, x0, y0, width, height, mutation_size):
  2258. saw_vertices = self._get_sawtooth_vertices(x0, y0, width,
  2259. height, mutation_size)
  2260. return Path(saw_vertices, closed=True)
  2261. @_register_style(_style_list)
  2262. class Roundtooth(Sawtooth):
  2263. """A box with a rounded sawtooth outline."""
  2264. def __call__(self, x0, y0, width, height, mutation_size):
  2265. saw_vertices = self._get_sawtooth_vertices(x0, y0,
  2266. width, height,
  2267. mutation_size)
  2268. # Add a trailing vertex to allow us to close the polygon correctly
  2269. saw_vertices = np.concatenate([saw_vertices, [saw_vertices[0]]])
  2270. codes = ([Path.MOVETO] +
  2271. [Path.CURVE3, Path.CURVE3] * ((len(saw_vertices)-1)//2) +
  2272. [Path.CLOSEPOLY])
  2273. return Path(saw_vertices, codes)
  2274. @_docstring.interpd
  2275. class ConnectionStyle(_Style):
  2276. """
  2277. `ConnectionStyle` is a container class which defines
  2278. several connectionstyle classes, which is used to create a path
  2279. between two points. These are mainly used with `FancyArrowPatch`.
  2280. A connectionstyle object can be either created as::
  2281. ConnectionStyle.Arc3(rad=0.2)
  2282. or::
  2283. ConnectionStyle("Arc3", rad=0.2)
  2284. or::
  2285. ConnectionStyle("Arc3, rad=0.2")
  2286. The following classes are defined
  2287. %(ConnectionStyle:table)s
  2288. An instance of any connection style class is a callable object,
  2289. whose call signature is::
  2290. __call__(self, posA, posB,
  2291. patchA=None, patchB=None,
  2292. shrinkA=2., shrinkB=2.)
  2293. and it returns a `.Path` instance. *posA* and *posB* are
  2294. tuples of (x, y) coordinates of the two points to be
  2295. connected. *patchA* (or *patchB*) is given, the returned path is
  2296. clipped so that it start (or end) from the boundary of the
  2297. patch. The path is further shrunk by *shrinkA* (or *shrinkB*)
  2298. which is given in points.
  2299. """
  2300. _style_list = {}
  2301. class _Base:
  2302. """
  2303. A base class for connectionstyle classes. The subclass needs
  2304. to implement a *connect* method whose call signature is::
  2305. connect(posA, posB)
  2306. where posA and posB are tuples of x, y coordinates to be
  2307. connected. The method needs to return a path connecting two
  2308. points. This base class defines a __call__ method, and a few
  2309. helper methods.
  2310. """
  2311. def _in_patch(self, patch):
  2312. """
  2313. Return a predicate function testing whether a point *xy* is
  2314. contained in *patch*.
  2315. """
  2316. return lambda xy: patch.contains(
  2317. SimpleNamespace(x=xy[0], y=xy[1]))[0]
  2318. def _clip(self, path, in_start, in_stop):
  2319. """
  2320. Clip *path* at its start by the region where *in_start* returns
  2321. True, and at its stop by the region where *in_stop* returns True.
  2322. The original path is assumed to start in the *in_start* region and
  2323. to stop in the *in_stop* region.
  2324. """
  2325. if in_start:
  2326. try:
  2327. _, path = split_path_inout(path, in_start)
  2328. except ValueError:
  2329. pass
  2330. if in_stop:
  2331. try:
  2332. path, _ = split_path_inout(path, in_stop)
  2333. except ValueError:
  2334. pass
  2335. return path
  2336. def __call__(self, posA, posB,
  2337. shrinkA=2., shrinkB=2., patchA=None, patchB=None):
  2338. """
  2339. Call the *connect* method to create a path between *posA* and
  2340. *posB*; then clip and shrink the path.
  2341. """
  2342. path = self.connect(posA, posB)
  2343. path = self._clip(
  2344. path,
  2345. self._in_patch(patchA) if patchA else None,
  2346. self._in_patch(patchB) if patchB else None,
  2347. )
  2348. path = self._clip(
  2349. path,
  2350. inside_circle(*path.vertices[0], shrinkA) if shrinkA else None,
  2351. inside_circle(*path.vertices[-1], shrinkB) if shrinkB else None
  2352. )
  2353. return path
  2354. @_register_style(_style_list)
  2355. class Arc3(_Base):
  2356. """
  2357. Creates a simple quadratic Bézier curve between two
  2358. points. The curve is created so that the middle control point
  2359. (C1) is located at the same distance from the start (C0) and
  2360. end points(C2) and the distance of the C1 to the line
  2361. connecting C0-C2 is *rad* times the distance of C0-C2.
  2362. """
  2363. def __init__(self, rad=0.):
  2364. """
  2365. Parameters
  2366. ----------
  2367. rad : float
  2368. Curvature of the curve.
  2369. """
  2370. self.rad = rad
  2371. def connect(self, posA, posB):
  2372. x1, y1 = posA
  2373. x2, y2 = posB
  2374. x12, y12 = (x1 + x2) / 2., (y1 + y2) / 2.
  2375. dx, dy = x2 - x1, y2 - y1
  2376. f = self.rad
  2377. cx, cy = x12 + f * dy, y12 - f * dx
  2378. vertices = [(x1, y1),
  2379. (cx, cy),
  2380. (x2, y2)]
  2381. codes = [Path.MOVETO,
  2382. Path.CURVE3,
  2383. Path.CURVE3]
  2384. return Path(vertices, codes)
  2385. @_register_style(_style_list)
  2386. class Angle3(_Base):
  2387. """
  2388. Creates a simple quadratic Bézier curve between two points. The middle
  2389. control point is placed at the intersecting point of two lines which
  2390. cross the start and end point, and have a slope of *angleA* and
  2391. *angleB*, respectively.
  2392. """
  2393. def __init__(self, angleA=90, angleB=0):
  2394. """
  2395. Parameters
  2396. ----------
  2397. angleA : float
  2398. Starting angle of the path.
  2399. angleB : float
  2400. Ending angle of the path.
  2401. """
  2402. self.angleA = angleA
  2403. self.angleB = angleB
  2404. def connect(self, posA, posB):
  2405. x1, y1 = posA
  2406. x2, y2 = posB
  2407. cosA = math.cos(math.radians(self.angleA))
  2408. sinA = math.sin(math.radians(self.angleA))
  2409. cosB = math.cos(math.radians(self.angleB))
  2410. sinB = math.sin(math.radians(self.angleB))
  2411. cx, cy = get_intersection(x1, y1, cosA, sinA,
  2412. x2, y2, cosB, sinB)
  2413. vertices = [(x1, y1), (cx, cy), (x2, y2)]
  2414. codes = [Path.MOVETO, Path.CURVE3, Path.CURVE3]
  2415. return Path(vertices, codes)
  2416. @_register_style(_style_list)
  2417. class Angle(_Base):
  2418. """
  2419. Creates a piecewise continuous quadratic Bézier path between two
  2420. points. The path has a one passing-through point placed at the
  2421. intersecting point of two lines which cross the start and end point,
  2422. and have a slope of *angleA* and *angleB*, respectively.
  2423. The connecting edges are rounded with *rad*.
  2424. """
  2425. def __init__(self, angleA=90, angleB=0, rad=0.):
  2426. """
  2427. Parameters
  2428. ----------
  2429. angleA : float
  2430. Starting angle of the path.
  2431. angleB : float
  2432. Ending angle of the path.
  2433. rad : float
  2434. Rounding radius of the edge.
  2435. """
  2436. self.angleA = angleA
  2437. self.angleB = angleB
  2438. self.rad = rad
  2439. def connect(self, posA, posB):
  2440. x1, y1 = posA
  2441. x2, y2 = posB
  2442. cosA = math.cos(math.radians(self.angleA))
  2443. sinA = math.sin(math.radians(self.angleA))
  2444. cosB = math.cos(math.radians(self.angleB))
  2445. sinB = math.sin(math.radians(self.angleB))
  2446. cx, cy = get_intersection(x1, y1, cosA, sinA,
  2447. x2, y2, cosB, sinB)
  2448. vertices = [(x1, y1)]
  2449. codes = [Path.MOVETO]
  2450. if self.rad == 0.:
  2451. vertices.append((cx, cy))
  2452. codes.append(Path.LINETO)
  2453. else:
  2454. dx1, dy1 = x1 - cx, y1 - cy
  2455. d1 = np.hypot(dx1, dy1)
  2456. f1 = self.rad / d1
  2457. dx2, dy2 = x2 - cx, y2 - cy
  2458. d2 = np.hypot(dx2, dy2)
  2459. f2 = self.rad / d2
  2460. vertices.extend([(cx + dx1 * f1, cy + dy1 * f1),
  2461. (cx, cy),
  2462. (cx + dx2 * f2, cy + dy2 * f2)])
  2463. codes.extend([Path.LINETO, Path.CURVE3, Path.CURVE3])
  2464. vertices.append((x2, y2))
  2465. codes.append(Path.LINETO)
  2466. return Path(vertices, codes)
  2467. @_register_style(_style_list)
  2468. class Arc(_Base):
  2469. """
  2470. Creates a piecewise continuous quadratic Bézier path between two
  2471. points. The path can have two passing-through points, a
  2472. point placed at the distance of *armA* and angle of *angleA* from
  2473. point A, another point with respect to point B. The edges are
  2474. rounded with *rad*.
  2475. """
  2476. def __init__(self, angleA=0, angleB=0, armA=None, armB=None, rad=0.):
  2477. """
  2478. Parameters
  2479. ----------
  2480. angleA : float
  2481. Starting angle of the path.
  2482. angleB : float
  2483. Ending angle of the path.
  2484. armA : float or None
  2485. Length of the starting arm.
  2486. armB : float or None
  2487. Length of the ending arm.
  2488. rad : float
  2489. Rounding radius of the edges.
  2490. """
  2491. self.angleA = angleA
  2492. self.angleB = angleB
  2493. self.armA = armA
  2494. self.armB = armB
  2495. self.rad = rad
  2496. def connect(self, posA, posB):
  2497. x1, y1 = posA
  2498. x2, y2 = posB
  2499. vertices = [(x1, y1)]
  2500. rounded = []
  2501. codes = [Path.MOVETO]
  2502. if self.armA:
  2503. cosA = math.cos(math.radians(self.angleA))
  2504. sinA = math.sin(math.radians(self.angleA))
  2505. # x_armA, y_armB
  2506. d = self.armA - self.rad
  2507. rounded.append((x1 + d * cosA, y1 + d * sinA))
  2508. d = self.armA
  2509. rounded.append((x1 + d * cosA, y1 + d * sinA))
  2510. if self.armB:
  2511. cosB = math.cos(math.radians(self.angleB))
  2512. sinB = math.sin(math.radians(self.angleB))
  2513. x_armB, y_armB = x2 + self.armB * cosB, y2 + self.armB * sinB
  2514. if rounded:
  2515. xp, yp = rounded[-1]
  2516. dx, dy = x_armB - xp, y_armB - yp
  2517. dd = (dx * dx + dy * dy) ** .5
  2518. rounded.append((xp + self.rad * dx / dd,
  2519. yp + self.rad * dy / dd))
  2520. vertices.extend(rounded)
  2521. codes.extend([Path.LINETO,
  2522. Path.CURVE3,
  2523. Path.CURVE3])
  2524. else:
  2525. xp, yp = vertices[-1]
  2526. dx, dy = x_armB - xp, y_armB - yp
  2527. dd = (dx * dx + dy * dy) ** .5
  2528. d = dd - self.rad
  2529. rounded = [(xp + d * dx / dd, yp + d * dy / dd),
  2530. (x_armB, y_armB)]
  2531. if rounded:
  2532. xp, yp = rounded[-1]
  2533. dx, dy = x2 - xp, y2 - yp
  2534. dd = (dx * dx + dy * dy) ** .5
  2535. rounded.append((xp + self.rad * dx / dd,
  2536. yp + self.rad * dy / dd))
  2537. vertices.extend(rounded)
  2538. codes.extend([Path.LINETO,
  2539. Path.CURVE3,
  2540. Path.CURVE3])
  2541. vertices.append((x2, y2))
  2542. codes.append(Path.LINETO)
  2543. return Path(vertices, codes)
  2544. @_register_style(_style_list)
  2545. class Bar(_Base):
  2546. """
  2547. A line with *angle* between A and B with *armA* and *armB*. One of the
  2548. arms is extended so that they are connected in a right angle. The
  2549. length of *armA* is determined by (*armA* + *fraction* x AB distance).
  2550. Same for *armB*.
  2551. """
  2552. def __init__(self, armA=0., armB=0., fraction=0.3, angle=None):
  2553. """
  2554. Parameters
  2555. ----------
  2556. armA : float
  2557. Minimum length of armA.
  2558. armB : float
  2559. Minimum length of armB.
  2560. fraction : float
  2561. A fraction of the distance between two points that will be
  2562. added to armA and armB.
  2563. angle : float or None
  2564. Angle of the connecting line (if None, parallel to A and B).
  2565. """
  2566. self.armA = armA
  2567. self.armB = armB
  2568. self.fraction = fraction
  2569. self.angle = angle
  2570. def connect(self, posA, posB):
  2571. x1, y1 = posA
  2572. x20, y20 = x2, y2 = posB
  2573. theta1 = math.atan2(y2 - y1, x2 - x1)
  2574. dx, dy = x2 - x1, y2 - y1
  2575. dd = (dx * dx + dy * dy) ** .5
  2576. ddx, ddy = dx / dd, dy / dd
  2577. armA, armB = self.armA, self.armB
  2578. if self.angle is not None:
  2579. theta0 = np.deg2rad(self.angle)
  2580. dtheta = theta1 - theta0
  2581. dl = dd * math.sin(dtheta)
  2582. dL = dd * math.cos(dtheta)
  2583. x2, y2 = x1 + dL * math.cos(theta0), y1 + dL * math.sin(theta0)
  2584. armB = armB - dl
  2585. # update
  2586. dx, dy = x2 - x1, y2 - y1
  2587. dd2 = (dx * dx + dy * dy) ** .5
  2588. ddx, ddy = dx / dd2, dy / dd2
  2589. arm = max(armA, armB)
  2590. f = self.fraction * dd + arm
  2591. cx1, cy1 = x1 + f * ddy, y1 - f * ddx
  2592. cx2, cy2 = x2 + f * ddy, y2 - f * ddx
  2593. vertices = [(x1, y1),
  2594. (cx1, cy1),
  2595. (cx2, cy2),
  2596. (x20, y20)]
  2597. codes = [Path.MOVETO,
  2598. Path.LINETO,
  2599. Path.LINETO,
  2600. Path.LINETO]
  2601. return Path(vertices, codes)
  2602. def _point_along_a_line(x0, y0, x1, y1, d):
  2603. """
  2604. Return the point on the line connecting (*x0*, *y0*) -- (*x1*, *y1*) whose
  2605. distance from (*x0*, *y0*) is *d*.
  2606. """
  2607. dx, dy = x0 - x1, y0 - y1
  2608. ff = d / (dx * dx + dy * dy) ** .5
  2609. x2, y2 = x0 - ff * dx, y0 - ff * dy
  2610. return x2, y2
  2611. @_docstring.interpd
  2612. class ArrowStyle(_Style):
  2613. """
  2614. `ArrowStyle` is a container class which defines several
  2615. arrowstyle classes, which is used to create an arrow path along a
  2616. given path. These are mainly used with `FancyArrowPatch`.
  2617. An arrowstyle object can be either created as::
  2618. ArrowStyle.Fancy(head_length=.4, head_width=.4, tail_width=.4)
  2619. or::
  2620. ArrowStyle("Fancy", head_length=.4, head_width=.4, tail_width=.4)
  2621. or::
  2622. ArrowStyle("Fancy, head_length=.4, head_width=.4, tail_width=.4")
  2623. The following classes are defined
  2624. %(ArrowStyle:table)s
  2625. For an overview of the visual appearance, see
  2626. :doc:`/gallery/text_labels_and_annotations/fancyarrow_demo`.
  2627. An instance of any arrow style class is a callable object,
  2628. whose call signature is::
  2629. __call__(self, path, mutation_size, linewidth, aspect_ratio=1.)
  2630. and it returns a tuple of a `.Path` instance and a boolean
  2631. value. *path* is a `.Path` instance along which the arrow
  2632. will be drawn. *mutation_size* and *aspect_ratio* have the same
  2633. meaning as in `BoxStyle`. *linewidth* is a line width to be
  2634. stroked. This is meant to be used to correct the location of the
  2635. head so that it does not overshoot the destination point, but not all
  2636. classes support it.
  2637. Notes
  2638. -----
  2639. *angleA* and *angleB* specify the orientation of the bracket, as either a
  2640. clockwise or counterclockwise angle depending on the arrow type. 0 degrees
  2641. means perpendicular to the line connecting the arrow's head and tail.
  2642. .. plot:: gallery/text_labels_and_annotations/angles_on_bracket_arrows.py
  2643. """
  2644. _style_list = {}
  2645. class _Base:
  2646. """
  2647. Arrow Transmuter Base class
  2648. ArrowTransmuterBase and its derivatives are used to make a fancy
  2649. arrow around a given path. The __call__ method returns a path
  2650. (which will be used to create a PathPatch instance) and a boolean
  2651. value indicating the path is open therefore is not fillable. This
  2652. class is not an artist and actual drawing of the fancy arrow is
  2653. done by the FancyArrowPatch class.
  2654. """
  2655. # The derived classes are required to be able to be initialized
  2656. # w/o arguments, i.e., all its argument (except self) must have
  2657. # the default values.
  2658. @staticmethod
  2659. def ensure_quadratic_bezier(path):
  2660. """
  2661. Some ArrowStyle classes only works with a simple quadratic
  2662. Bézier curve (created with `.ConnectionStyle.Arc3` or
  2663. `.ConnectionStyle.Angle3`). This static method checks if the
  2664. provided path is a simple quadratic Bézier curve and returns its
  2665. control points if true.
  2666. """
  2667. segments = list(path.iter_segments())
  2668. if (len(segments) != 2 or segments[0][1] != Path.MOVETO or
  2669. segments[1][1] != Path.CURVE3):
  2670. raise ValueError(
  2671. "'path' is not a valid quadratic Bezier curve")
  2672. return [*segments[0][0], *segments[1][0]]
  2673. def transmute(self, path, mutation_size, linewidth):
  2674. """
  2675. The transmute method is the very core of the ArrowStyle class and
  2676. must be overridden in the subclasses. It receives the *path*
  2677. object along which the arrow will be drawn, and the
  2678. *mutation_size*, with which the arrow head etc. will be scaled.
  2679. The *linewidth* may be used to adjust the path so that it does not
  2680. pass beyond the given points. It returns a tuple of a `.Path`
  2681. instance and a boolean. The boolean value indicate whether the
  2682. path can be filled or not. The return value can also be a list of
  2683. paths and list of booleans of the same length.
  2684. """
  2685. raise NotImplementedError('Derived must override')
  2686. def __call__(self, path, mutation_size, linewidth,
  2687. aspect_ratio=1.):
  2688. """
  2689. The __call__ method is a thin wrapper around the transmute method
  2690. and takes care of the aspect ratio.
  2691. """
  2692. if aspect_ratio is not None:
  2693. # Squeeze the given height by the aspect_ratio
  2694. vertices = path.vertices / [1, aspect_ratio]
  2695. path_shrunk = Path(vertices, path.codes)
  2696. # call transmute method with squeezed height.
  2697. path_mutated, fillable = self.transmute(path_shrunk,
  2698. mutation_size,
  2699. linewidth)
  2700. if np.iterable(fillable):
  2701. # Restore the height
  2702. path_list = [Path(p.vertices * [1, aspect_ratio], p.codes)
  2703. for p in path_mutated]
  2704. return path_list, fillable
  2705. else:
  2706. return path_mutated, fillable
  2707. else:
  2708. return self.transmute(path, mutation_size, linewidth)
  2709. class _Curve(_Base):
  2710. """
  2711. A simple arrow which will work with any path instance. The
  2712. returned path is the concatenation of the original path, and at
  2713. most two paths representing the arrow head or bracket at the start
  2714. point and at the end point. The arrow heads can be either open
  2715. or closed.
  2716. """
  2717. arrow = "-"
  2718. fillbegin = fillend = False # Whether arrows are filled.
  2719. def __init__(self, head_length=.4, head_width=.2, widthA=1., widthB=1.,
  2720. lengthA=0.2, lengthB=0.2, angleA=0, angleB=0, scaleA=None,
  2721. scaleB=None):
  2722. """
  2723. Parameters
  2724. ----------
  2725. head_length : float, default: 0.4
  2726. Length of the arrow head, relative to *mutation_size*.
  2727. head_width : float, default: 0.2
  2728. Width of the arrow head, relative to *mutation_size*.
  2729. widthA, widthB : float, default: 1.0
  2730. Width of the bracket.
  2731. lengthA, lengthB : float, default: 0.2
  2732. Length of the bracket.
  2733. angleA, angleB : float, default: 0
  2734. Orientation of the bracket, as a counterclockwise angle.
  2735. 0 degrees means perpendicular to the line.
  2736. scaleA, scaleB : float, default: *mutation_size*
  2737. The scale of the brackets.
  2738. """
  2739. self.head_length, self.head_width = head_length, head_width
  2740. self.widthA, self.widthB = widthA, widthB
  2741. self.lengthA, self.lengthB = lengthA, lengthB
  2742. self.angleA, self.angleB = angleA, angleB
  2743. self.scaleA, self.scaleB = scaleA, scaleB
  2744. self._beginarrow_head = False
  2745. self._beginarrow_bracket = False
  2746. self._endarrow_head = False
  2747. self._endarrow_bracket = False
  2748. if "-" not in self.arrow:
  2749. raise ValueError("arrow must have the '-' between "
  2750. "the two heads")
  2751. beginarrow, endarrow = self.arrow.split("-", 1)
  2752. if beginarrow == "<":
  2753. self._beginarrow_head = True
  2754. self._beginarrow_bracket = False
  2755. elif beginarrow == "<|":
  2756. self._beginarrow_head = True
  2757. self._beginarrow_bracket = False
  2758. self.fillbegin = True
  2759. elif beginarrow in ("]", "|"):
  2760. self._beginarrow_head = False
  2761. self._beginarrow_bracket = True
  2762. if endarrow == ">":
  2763. self._endarrow_head = True
  2764. self._endarrow_bracket = False
  2765. elif endarrow == "|>":
  2766. self._endarrow_head = True
  2767. self._endarrow_bracket = False
  2768. self.fillend = True
  2769. elif endarrow in ("[", "|"):
  2770. self._endarrow_head = False
  2771. self._endarrow_bracket = True
  2772. super().__init__()
  2773. def _get_arrow_wedge(self, x0, y0, x1, y1,
  2774. head_dist, cos_t, sin_t, linewidth):
  2775. """
  2776. Return the paths for arrow heads. Since arrow lines are
  2777. drawn with capstyle=projected, The arrow goes beyond the
  2778. desired point. This method also returns the amount of the path
  2779. to be shrunken so that it does not overshoot.
  2780. """
  2781. # arrow from x0, y0 to x1, y1
  2782. dx, dy = x0 - x1, y0 - y1
  2783. cp_distance = np.hypot(dx, dy)
  2784. # pad_projected : amount of pad to account the
  2785. # overshooting of the projection of the wedge
  2786. pad_projected = (.5 * linewidth / sin_t)
  2787. # Account for division by zero
  2788. if cp_distance == 0:
  2789. cp_distance = 1
  2790. # apply pad for projected edge
  2791. ddx = pad_projected * dx / cp_distance
  2792. ddy = pad_projected * dy / cp_distance
  2793. # offset for arrow wedge
  2794. dx = dx / cp_distance * head_dist
  2795. dy = dy / cp_distance * head_dist
  2796. dx1, dy1 = cos_t * dx + sin_t * dy, -sin_t * dx + cos_t * dy
  2797. dx2, dy2 = cos_t * dx - sin_t * dy, sin_t * dx + cos_t * dy
  2798. vertices_arrow = [(x1 + ddx + dx1, y1 + ddy + dy1),
  2799. (x1 + ddx, y1 + ddy),
  2800. (x1 + ddx + dx2, y1 + ddy + dy2)]
  2801. codes_arrow = [Path.MOVETO,
  2802. Path.LINETO,
  2803. Path.LINETO]
  2804. return vertices_arrow, codes_arrow, ddx, ddy
  2805. def _get_bracket(self, x0, y0,
  2806. x1, y1, width, length, angle):
  2807. cos_t, sin_t = get_cos_sin(x1, y1, x0, y0)
  2808. # arrow from x0, y0 to x1, y1
  2809. from matplotlib.bezier import get_normal_points
  2810. x1, y1, x2, y2 = get_normal_points(x0, y0, cos_t, sin_t, width)
  2811. dx, dy = length * cos_t, length * sin_t
  2812. vertices_arrow = [(x1 + dx, y1 + dy),
  2813. (x1, y1),
  2814. (x2, y2),
  2815. (x2 + dx, y2 + dy)]
  2816. codes_arrow = [Path.MOVETO,
  2817. Path.LINETO,
  2818. Path.LINETO,
  2819. Path.LINETO]
  2820. if angle:
  2821. trans = transforms.Affine2D().rotate_deg_around(x0, y0, angle)
  2822. vertices_arrow = trans.transform(vertices_arrow)
  2823. return vertices_arrow, codes_arrow
  2824. def transmute(self, path, mutation_size, linewidth):
  2825. # docstring inherited
  2826. if self._beginarrow_head or self._endarrow_head:
  2827. head_length = self.head_length * mutation_size
  2828. head_width = self.head_width * mutation_size
  2829. head_dist = np.hypot(head_length, head_width)
  2830. cos_t, sin_t = head_length / head_dist, head_width / head_dist
  2831. scaleA = mutation_size if self.scaleA is None else self.scaleA
  2832. scaleB = mutation_size if self.scaleB is None else self.scaleB
  2833. # begin arrow
  2834. x0, y0 = path.vertices[0]
  2835. x1, y1 = path.vertices[1]
  2836. # If there is no room for an arrow and a line, then skip the arrow
  2837. has_begin_arrow = self._beginarrow_head and (x0, y0) != (x1, y1)
  2838. verticesA, codesA, ddxA, ddyA = (
  2839. self._get_arrow_wedge(x1, y1, x0, y0,
  2840. head_dist, cos_t, sin_t, linewidth)
  2841. if has_begin_arrow
  2842. else ([], [], 0, 0)
  2843. )
  2844. # end arrow
  2845. x2, y2 = path.vertices[-2]
  2846. x3, y3 = path.vertices[-1]
  2847. # If there is no room for an arrow and a line, then skip the arrow
  2848. has_end_arrow = self._endarrow_head and (x2, y2) != (x3, y3)
  2849. verticesB, codesB, ddxB, ddyB = (
  2850. self._get_arrow_wedge(x2, y2, x3, y3,
  2851. head_dist, cos_t, sin_t, linewidth)
  2852. if has_end_arrow
  2853. else ([], [], 0, 0)
  2854. )
  2855. # This simple code will not work if ddx, ddy is greater than the
  2856. # separation between vertices.
  2857. paths = [Path(np.concatenate([[(x0 + ddxA, y0 + ddyA)],
  2858. path.vertices[1:-1],
  2859. [(x3 + ddxB, y3 + ddyB)]]),
  2860. path.codes)]
  2861. fills = [False]
  2862. if has_begin_arrow:
  2863. if self.fillbegin:
  2864. paths.append(
  2865. Path([*verticesA, (0, 0)], [*codesA, Path.CLOSEPOLY]))
  2866. fills.append(True)
  2867. else:
  2868. paths.append(Path(verticesA, codesA))
  2869. fills.append(False)
  2870. elif self._beginarrow_bracket:
  2871. x0, y0 = path.vertices[0]
  2872. x1, y1 = path.vertices[1]
  2873. verticesA, codesA = self._get_bracket(x0, y0, x1, y1,
  2874. self.widthA * scaleA,
  2875. self.lengthA * scaleA,
  2876. self.angleA)
  2877. paths.append(Path(verticesA, codesA))
  2878. fills.append(False)
  2879. if has_end_arrow:
  2880. if self.fillend:
  2881. fills.append(True)
  2882. paths.append(
  2883. Path([*verticesB, (0, 0)], [*codesB, Path.CLOSEPOLY]))
  2884. else:
  2885. fills.append(False)
  2886. paths.append(Path(verticesB, codesB))
  2887. elif self._endarrow_bracket:
  2888. x0, y0 = path.vertices[-1]
  2889. x1, y1 = path.vertices[-2]
  2890. verticesB, codesB = self._get_bracket(x0, y0, x1, y1,
  2891. self.widthB * scaleB,
  2892. self.lengthB * scaleB,
  2893. self.angleB)
  2894. paths.append(Path(verticesB, codesB))
  2895. fills.append(False)
  2896. return paths, fills
  2897. @_register_style(_style_list, name="-")
  2898. class Curve(_Curve):
  2899. """A simple curve without any arrow head."""
  2900. def __init__(self): # hide head_length, head_width
  2901. # These attributes (whose values come from backcompat) only matter
  2902. # if someone modifies beginarrow/etc. on an ArrowStyle instance.
  2903. super().__init__(head_length=.2, head_width=.1)
  2904. @_register_style(_style_list, name="<-")
  2905. class CurveA(_Curve):
  2906. """An arrow with a head at its start point."""
  2907. arrow = "<-"
  2908. @_register_style(_style_list, name="->")
  2909. class CurveB(_Curve):
  2910. """An arrow with a head at its end point."""
  2911. arrow = "->"
  2912. @_register_style(_style_list, name="<->")
  2913. class CurveAB(_Curve):
  2914. """An arrow with heads both at the start and the end point."""
  2915. arrow = "<->"
  2916. @_register_style(_style_list, name="<|-")
  2917. class CurveFilledA(_Curve):
  2918. """An arrow with filled triangle head at the start."""
  2919. arrow = "<|-"
  2920. @_register_style(_style_list, name="-|>")
  2921. class CurveFilledB(_Curve):
  2922. """An arrow with filled triangle head at the end."""
  2923. arrow = "-|>"
  2924. @_register_style(_style_list, name="<|-|>")
  2925. class CurveFilledAB(_Curve):
  2926. """An arrow with filled triangle heads at both ends."""
  2927. arrow = "<|-|>"
  2928. @_register_style(_style_list, name="]-")
  2929. class BracketA(_Curve):
  2930. """An arrow with an outward square bracket at its start."""
  2931. arrow = "]-"
  2932. def __init__(self, widthA=1., lengthA=0.2, angleA=0):
  2933. """
  2934. Parameters
  2935. ----------
  2936. widthA : float, default: 1.0
  2937. Width of the bracket.
  2938. lengthA : float, default: 0.2
  2939. Length of the bracket.
  2940. angleA : float, default: 0 degrees
  2941. Orientation of the bracket, as a counterclockwise angle.
  2942. 0 degrees means perpendicular to the line.
  2943. """
  2944. super().__init__(widthA=widthA, lengthA=lengthA, angleA=angleA)
  2945. @_register_style(_style_list, name="-[")
  2946. class BracketB(_Curve):
  2947. """An arrow with an outward square bracket at its end."""
  2948. arrow = "-["
  2949. def __init__(self, widthB=1., lengthB=0.2, angleB=0):
  2950. """
  2951. Parameters
  2952. ----------
  2953. widthB : float, default: 1.0
  2954. Width of the bracket.
  2955. lengthB : float, default: 0.2
  2956. Length of the bracket.
  2957. angleB : float, default: 0 degrees
  2958. Orientation of the bracket, as a counterclockwise angle.
  2959. 0 degrees means perpendicular to the line.
  2960. """
  2961. super().__init__(widthB=widthB, lengthB=lengthB, angleB=angleB)
  2962. @_register_style(_style_list, name="]-[")
  2963. class BracketAB(_Curve):
  2964. """An arrow with outward square brackets at both ends."""
  2965. arrow = "]-["
  2966. def __init__(self,
  2967. widthA=1., lengthA=0.2, angleA=0,
  2968. widthB=1., lengthB=0.2, angleB=0):
  2969. """
  2970. Parameters
  2971. ----------
  2972. widthA, widthB : float, default: 1.0
  2973. Width of the bracket.
  2974. lengthA, lengthB : float, default: 0.2
  2975. Length of the bracket.
  2976. angleA, angleB : float, default: 0 degrees
  2977. Orientation of the bracket, as a counterclockwise angle.
  2978. 0 degrees means perpendicular to the line.
  2979. """
  2980. super().__init__(widthA=widthA, lengthA=lengthA, angleA=angleA,
  2981. widthB=widthB, lengthB=lengthB, angleB=angleB)
  2982. @_register_style(_style_list, name="|-|")
  2983. class BarAB(_Curve):
  2984. """An arrow with vertical bars ``|`` at both ends."""
  2985. arrow = "|-|"
  2986. def __init__(self, widthA=1., angleA=0, widthB=1., angleB=0):
  2987. """
  2988. Parameters
  2989. ----------
  2990. widthA, widthB : float, default: 1.0
  2991. Width of the bracket.
  2992. angleA, angleB : float, default: 0 degrees
  2993. Orientation of the bracket, as a counterclockwise angle.
  2994. 0 degrees means perpendicular to the line.
  2995. """
  2996. super().__init__(widthA=widthA, lengthA=0, angleA=angleA,
  2997. widthB=widthB, lengthB=0, angleB=angleB)
  2998. @_register_style(_style_list, name=']->')
  2999. class BracketCurve(_Curve):
  3000. """
  3001. An arrow with an outward square bracket at its start and a head at
  3002. the end.
  3003. """
  3004. arrow = "]->"
  3005. def __init__(self, widthA=1., lengthA=0.2, angleA=None):
  3006. """
  3007. Parameters
  3008. ----------
  3009. widthA : float, default: 1.0
  3010. Width of the bracket.
  3011. lengthA : float, default: 0.2
  3012. Length of the bracket.
  3013. angleA : float, default: 0 degrees
  3014. Orientation of the bracket, as a counterclockwise angle.
  3015. 0 degrees means perpendicular to the line.
  3016. """
  3017. super().__init__(widthA=widthA, lengthA=lengthA, angleA=angleA)
  3018. @_register_style(_style_list, name='<-[')
  3019. class CurveBracket(_Curve):
  3020. """
  3021. An arrow with an outward square bracket at its end and a head at
  3022. the start.
  3023. """
  3024. arrow = "<-["
  3025. def __init__(self, widthB=1., lengthB=0.2, angleB=None):
  3026. """
  3027. Parameters
  3028. ----------
  3029. widthB : float, default: 1.0
  3030. Width of the bracket.
  3031. lengthB : float, default: 0.2
  3032. Length of the bracket.
  3033. angleB : float, default: 0 degrees
  3034. Orientation of the bracket, as a counterclockwise angle.
  3035. 0 degrees means perpendicular to the line.
  3036. """
  3037. super().__init__(widthB=widthB, lengthB=lengthB, angleB=angleB)
  3038. @_register_style(_style_list)
  3039. class Simple(_Base):
  3040. """A simple arrow. Only works with a quadratic Bézier curve."""
  3041. def __init__(self, head_length=.5, head_width=.5, tail_width=.2):
  3042. """
  3043. Parameters
  3044. ----------
  3045. head_length : float, default: 0.5
  3046. Length of the arrow head.
  3047. head_width : float, default: 0.5
  3048. Width of the arrow head.
  3049. tail_width : float, default: 0.2
  3050. Width of the arrow tail.
  3051. """
  3052. self.head_length, self.head_width, self.tail_width = \
  3053. head_length, head_width, tail_width
  3054. super().__init__()
  3055. def transmute(self, path, mutation_size, linewidth):
  3056. # docstring inherited
  3057. x0, y0, x1, y1, x2, y2 = self.ensure_quadratic_bezier(path)
  3058. # divide the path into a head and a tail
  3059. head_length = self.head_length * mutation_size
  3060. in_f = inside_circle(x2, y2, head_length)
  3061. arrow_path = [(x0, y0), (x1, y1), (x2, y2)]
  3062. try:
  3063. arrow_out, arrow_in = \
  3064. split_bezier_intersecting_with_closedpath(arrow_path, in_f)
  3065. except NonIntersectingPathException:
  3066. # if this happens, make a straight line of the head_length
  3067. # long.
  3068. x0, y0 = _point_along_a_line(x2, y2, x1, y1, head_length)
  3069. x1n, y1n = 0.5 * (x0 + x2), 0.5 * (y0 + y2)
  3070. arrow_in = [(x0, y0), (x1n, y1n), (x2, y2)]
  3071. arrow_out = None
  3072. # head
  3073. head_width = self.head_width * mutation_size
  3074. head_left, head_right = make_wedged_bezier2(arrow_in,
  3075. head_width / 2., wm=.5)
  3076. # tail
  3077. if arrow_out is not None:
  3078. tail_width = self.tail_width * mutation_size
  3079. tail_left, tail_right = get_parallels(arrow_out,
  3080. tail_width / 2.)
  3081. patch_path = [(Path.MOVETO, tail_right[0]),
  3082. (Path.CURVE3, tail_right[1]),
  3083. (Path.CURVE3, tail_right[2]),
  3084. (Path.LINETO, head_right[0]),
  3085. (Path.CURVE3, head_right[1]),
  3086. (Path.CURVE3, head_right[2]),
  3087. (Path.CURVE3, head_left[1]),
  3088. (Path.CURVE3, head_left[0]),
  3089. (Path.LINETO, tail_left[2]),
  3090. (Path.CURVE3, tail_left[1]),
  3091. (Path.CURVE3, tail_left[0]),
  3092. (Path.LINETO, tail_right[0]),
  3093. (Path.CLOSEPOLY, tail_right[0]),
  3094. ]
  3095. else:
  3096. patch_path = [(Path.MOVETO, head_right[0]),
  3097. (Path.CURVE3, head_right[1]),
  3098. (Path.CURVE3, head_right[2]),
  3099. (Path.CURVE3, head_left[1]),
  3100. (Path.CURVE3, head_left[0]),
  3101. (Path.CLOSEPOLY, head_left[0]),
  3102. ]
  3103. path = Path([p for c, p in patch_path], [c for c, p in patch_path])
  3104. return path, True
  3105. @_register_style(_style_list)
  3106. class Fancy(_Base):
  3107. """A fancy arrow. Only works with a quadratic Bézier curve."""
  3108. def __init__(self, head_length=.4, head_width=.4, tail_width=.4):
  3109. """
  3110. Parameters
  3111. ----------
  3112. head_length : float, default: 0.4
  3113. Length of the arrow head.
  3114. head_width : float, default: 0.4
  3115. Width of the arrow head.
  3116. tail_width : float, default: 0.4
  3117. Width of the arrow tail.
  3118. """
  3119. self.head_length, self.head_width, self.tail_width = \
  3120. head_length, head_width, tail_width
  3121. super().__init__()
  3122. def transmute(self, path, mutation_size, linewidth):
  3123. # docstring inherited
  3124. x0, y0, x1, y1, x2, y2 = self.ensure_quadratic_bezier(path)
  3125. # divide the path into a head and a tail
  3126. head_length = self.head_length * mutation_size
  3127. arrow_path = [(x0, y0), (x1, y1), (x2, y2)]
  3128. # path for head
  3129. in_f = inside_circle(x2, y2, head_length)
  3130. try:
  3131. path_out, path_in = split_bezier_intersecting_with_closedpath(
  3132. arrow_path, in_f)
  3133. except NonIntersectingPathException:
  3134. # if this happens, make a straight line of the head_length
  3135. # long.
  3136. x0, y0 = _point_along_a_line(x2, y2, x1, y1, head_length)
  3137. x1n, y1n = 0.5 * (x0 + x2), 0.5 * (y0 + y2)
  3138. arrow_path = [(x0, y0), (x1n, y1n), (x2, y2)]
  3139. path_head = arrow_path
  3140. else:
  3141. path_head = path_in
  3142. # path for head
  3143. in_f = inside_circle(x2, y2, head_length * .8)
  3144. path_out, path_in = split_bezier_intersecting_with_closedpath(
  3145. arrow_path, in_f)
  3146. path_tail = path_out
  3147. # head
  3148. head_width = self.head_width * mutation_size
  3149. head_l, head_r = make_wedged_bezier2(path_head,
  3150. head_width / 2.,
  3151. wm=.6)
  3152. # tail
  3153. tail_width = self.tail_width * mutation_size
  3154. tail_left, tail_right = make_wedged_bezier2(path_tail,
  3155. tail_width * .5,
  3156. w1=1., wm=0.6, w2=0.3)
  3157. # path for head
  3158. in_f = inside_circle(x0, y0, tail_width * .3)
  3159. path_in, path_out = split_bezier_intersecting_with_closedpath(
  3160. arrow_path, in_f)
  3161. tail_start = path_in[-1]
  3162. head_right, head_left = head_r, head_l
  3163. patch_path = [(Path.MOVETO, tail_start),
  3164. (Path.LINETO, tail_right[0]),
  3165. (Path.CURVE3, tail_right[1]),
  3166. (Path.CURVE3, tail_right[2]),
  3167. (Path.LINETO, head_right[0]),
  3168. (Path.CURVE3, head_right[1]),
  3169. (Path.CURVE3, head_right[2]),
  3170. (Path.CURVE3, head_left[1]),
  3171. (Path.CURVE3, head_left[0]),
  3172. (Path.LINETO, tail_left[2]),
  3173. (Path.CURVE3, tail_left[1]),
  3174. (Path.CURVE3, tail_left[0]),
  3175. (Path.LINETO, tail_start),
  3176. (Path.CLOSEPOLY, tail_start),
  3177. ]
  3178. path = Path([p for c, p in patch_path], [c for c, p in patch_path])
  3179. return path, True
  3180. @_register_style(_style_list)
  3181. class Wedge(_Base):
  3182. """
  3183. Wedge(?) shape. Only works with a quadratic Bézier curve. The
  3184. start point has a width of the *tail_width* and the end point has a
  3185. width of 0. At the middle, the width is *shrink_factor*x*tail_width*.
  3186. """
  3187. def __init__(self, tail_width=.3, shrink_factor=0.5):
  3188. """
  3189. Parameters
  3190. ----------
  3191. tail_width : float, default: 0.3
  3192. Width of the tail.
  3193. shrink_factor : float, default: 0.5
  3194. Fraction of the arrow width at the middle point.
  3195. """
  3196. self.tail_width = tail_width
  3197. self.shrink_factor = shrink_factor
  3198. super().__init__()
  3199. def transmute(self, path, mutation_size, linewidth):
  3200. # docstring inherited
  3201. x0, y0, x1, y1, x2, y2 = self.ensure_quadratic_bezier(path)
  3202. arrow_path = [(x0, y0), (x1, y1), (x2, y2)]
  3203. b_plus, b_minus = make_wedged_bezier2(
  3204. arrow_path,
  3205. self.tail_width * mutation_size / 2.,
  3206. wm=self.shrink_factor)
  3207. patch_path = [(Path.MOVETO, b_plus[0]),
  3208. (Path.CURVE3, b_plus[1]),
  3209. (Path.CURVE3, b_plus[2]),
  3210. (Path.LINETO, b_minus[2]),
  3211. (Path.CURVE3, b_minus[1]),
  3212. (Path.CURVE3, b_minus[0]),
  3213. (Path.CLOSEPOLY, b_minus[0]),
  3214. ]
  3215. path = Path([p for c, p in patch_path], [c for c, p in patch_path])
  3216. return path, True
  3217. class FancyBboxPatch(Patch):
  3218. """
  3219. A fancy box around a rectangle with lower left at *xy* = (*x*, *y*)
  3220. with specified width and height.
  3221. `.FancyBboxPatch` is similar to `.Rectangle`, but it draws a fancy box
  3222. around the rectangle. The transformation of the rectangle box to the
  3223. fancy box is delegated to the style classes defined in `.BoxStyle`.
  3224. """
  3225. _edge_default = True
  3226. def __str__(self):
  3227. s = self.__class__.__name__ + "((%g, %g), width=%g, height=%g)"
  3228. return s % (self._x, self._y, self._width, self._height)
  3229. @_docstring.interpd
  3230. def __init__(self, xy, width, height, boxstyle="round", *,
  3231. mutation_scale=1, mutation_aspect=1, **kwargs):
  3232. """
  3233. Parameters
  3234. ----------
  3235. xy : (float, float)
  3236. The lower left corner of the box.
  3237. width : float
  3238. The width of the box.
  3239. height : float
  3240. The height of the box.
  3241. boxstyle : str or `~matplotlib.patches.BoxStyle`
  3242. The style of the fancy box. This can either be a `.BoxStyle`
  3243. instance or a string of the style name and optionally comma
  3244. separated attributes (e.g. "Round, pad=0.2"). This string is
  3245. passed to `.BoxStyle` to construct a `.BoxStyle` object. See
  3246. there for a full documentation.
  3247. The following box styles are available:
  3248. %(BoxStyle:table)s
  3249. mutation_scale : float, default: 1
  3250. Scaling factor applied to the attributes of the box style
  3251. (e.g. pad or rounding_size).
  3252. mutation_aspect : float, default: 1
  3253. The height of the rectangle will be squeezed by this value before
  3254. the mutation and the mutated box will be stretched by the inverse
  3255. of it. For example, this allows different horizontal and vertical
  3256. padding.
  3257. Other Parameters
  3258. ----------------
  3259. **kwargs : `~matplotlib.patches.Patch` properties
  3260. %(Patch:kwdoc)s
  3261. """
  3262. super().__init__(**kwargs)
  3263. self._x, self._y = xy
  3264. self._width = width
  3265. self._height = height
  3266. self.set_boxstyle(boxstyle)
  3267. self._mutation_scale = mutation_scale
  3268. self._mutation_aspect = mutation_aspect
  3269. self.stale = True
  3270. @_docstring.interpd
  3271. def set_boxstyle(self, boxstyle=None, **kwargs):
  3272. """
  3273. Set the box style, possibly with further attributes.
  3274. Attributes from the previous box style are not reused.
  3275. Without argument (or with ``boxstyle=None``), the available box styles
  3276. are returned as a human-readable string.
  3277. Parameters
  3278. ----------
  3279. boxstyle : str or `~matplotlib.patches.BoxStyle`
  3280. The style of the box: either a `.BoxStyle` instance, or a string,
  3281. which is the style name and optionally comma separated attributes
  3282. (e.g. "Round,pad=0.2"). Such a string is used to construct a
  3283. `.BoxStyle` object, as documented in that class.
  3284. The following box styles are available:
  3285. %(BoxStyle:table_and_accepts)s
  3286. **kwargs
  3287. Additional attributes for the box style. See the table above for
  3288. supported parameters.
  3289. Examples
  3290. --------
  3291. ::
  3292. set_boxstyle("Round,pad=0.2")
  3293. set_boxstyle("round", pad=0.2)
  3294. """
  3295. if boxstyle is None:
  3296. return BoxStyle.pprint_styles()
  3297. self._bbox_transmuter = (
  3298. BoxStyle(boxstyle, **kwargs)
  3299. if isinstance(boxstyle, str) else boxstyle)
  3300. self.stale = True
  3301. def get_boxstyle(self):
  3302. """Return the boxstyle object."""
  3303. return self._bbox_transmuter
  3304. def set_mutation_scale(self, scale):
  3305. """
  3306. Set the mutation scale.
  3307. Parameters
  3308. ----------
  3309. scale : float
  3310. """
  3311. self._mutation_scale = scale
  3312. self.stale = True
  3313. def get_mutation_scale(self):
  3314. """Return the mutation scale."""
  3315. return self._mutation_scale
  3316. def set_mutation_aspect(self, aspect):
  3317. """
  3318. Set the aspect ratio of the bbox mutation.
  3319. Parameters
  3320. ----------
  3321. aspect : float
  3322. """
  3323. self._mutation_aspect = aspect
  3324. self.stale = True
  3325. def get_mutation_aspect(self):
  3326. """Return the aspect ratio of the bbox mutation."""
  3327. return (self._mutation_aspect if self._mutation_aspect is not None
  3328. else 1) # backcompat.
  3329. def get_path(self):
  3330. """Return the mutated path of the rectangle."""
  3331. boxstyle = self.get_boxstyle()
  3332. m_aspect = self.get_mutation_aspect()
  3333. # Call boxstyle with y, height squeezed by aspect_ratio.
  3334. path = boxstyle(self._x, self._y / m_aspect,
  3335. self._width, self._height / m_aspect,
  3336. self.get_mutation_scale())
  3337. return Path(path.vertices * [1, m_aspect], path.codes) # Unsqueeze y.
  3338. # Following methods are borrowed from the Rectangle class.
  3339. def get_x(self):
  3340. """Return the left coord of the rectangle."""
  3341. return self._x
  3342. def get_y(self):
  3343. """Return the bottom coord of the rectangle."""
  3344. return self._y
  3345. def get_width(self):
  3346. """Return the width of the rectangle."""
  3347. return self._width
  3348. def get_height(self):
  3349. """Return the height of the rectangle."""
  3350. return self._height
  3351. def set_x(self, x):
  3352. """
  3353. Set the left coord of the rectangle.
  3354. Parameters
  3355. ----------
  3356. x : float
  3357. """
  3358. self._x = x
  3359. self.stale = True
  3360. def set_y(self, y):
  3361. """
  3362. Set the bottom coord of the rectangle.
  3363. Parameters
  3364. ----------
  3365. y : float
  3366. """
  3367. self._y = y
  3368. self.stale = True
  3369. def set_width(self, w):
  3370. """
  3371. Set the rectangle width.
  3372. Parameters
  3373. ----------
  3374. w : float
  3375. """
  3376. self._width = w
  3377. self.stale = True
  3378. def set_height(self, h):
  3379. """
  3380. Set the rectangle height.
  3381. Parameters
  3382. ----------
  3383. h : float
  3384. """
  3385. self._height = h
  3386. self.stale = True
  3387. def set_bounds(self, *args):
  3388. """
  3389. Set the bounds of the rectangle.
  3390. Call signatures::
  3391. set_bounds(left, bottom, width, height)
  3392. set_bounds((left, bottom, width, height))
  3393. Parameters
  3394. ----------
  3395. left, bottom : float
  3396. The coordinates of the bottom left corner of the rectangle.
  3397. width, height : float
  3398. The width/height of the rectangle.
  3399. """
  3400. if len(args) == 1:
  3401. l, b, w, h = args[0]
  3402. else:
  3403. l, b, w, h = args
  3404. self._x = l
  3405. self._y = b
  3406. self._width = w
  3407. self._height = h
  3408. self.stale = True
  3409. def get_bbox(self):
  3410. """Return the `.Bbox`."""
  3411. return transforms.Bbox.from_bounds(self._x, self._y,
  3412. self._width, self._height)
  3413. class FancyArrowPatch(Patch):
  3414. """
  3415. A fancy arrow patch.
  3416. It draws an arrow using the `ArrowStyle`. It is primarily used by the
  3417. `~.axes.Axes.annotate` method. For most purposes, use the annotate method for
  3418. drawing arrows.
  3419. The head and tail positions are fixed at the specified start and end points
  3420. of the arrow, but the size and shape (in display coordinates) of the arrow
  3421. does not change when the axis is moved or zoomed.
  3422. """
  3423. _edge_default = True
  3424. def __str__(self):
  3425. if self._posA_posB is not None:
  3426. (x1, y1), (x2, y2) = self._posA_posB
  3427. return f"{type(self).__name__}(({x1:g}, {y1:g})->({x2:g}, {y2:g}))"
  3428. else:
  3429. return f"{type(self).__name__}({self._path_original})"
  3430. @_docstring.interpd
  3431. def __init__(self, posA=None, posB=None, *,
  3432. path=None, arrowstyle="simple", connectionstyle="arc3",
  3433. patchA=None, patchB=None, shrinkA=2, shrinkB=2,
  3434. mutation_scale=1, mutation_aspect=1, **kwargs):
  3435. """
  3436. **Defining the arrow position and path**
  3437. There are two ways to define the arrow position and path:
  3438. - **Start, end and connection**:
  3439. The typical approach is to define the start and end points of the
  3440. arrow using *posA* and *posB*. The curve between these two can
  3441. further be configured using *connectionstyle*.
  3442. If given, the arrow curve is clipped by *patchA* and *patchB*,
  3443. allowing it to start/end at the border of these patches.
  3444. Additionally, the arrow curve can be shortened by *shrinkA* and *shrinkB*
  3445. to create a margin between start/end (after possible clipping) and the
  3446. drawn arrow.
  3447. - **path**: Alternatively if *path* is provided, an arrow is drawn along
  3448. this Path. In this case, *connectionstyle*, *patchA*, *patchB*,
  3449. *shrinkA*, and *shrinkB* are ignored.
  3450. **Styling**
  3451. The *arrowstyle* defines the styling of the arrow head, tail and shaft.
  3452. The resulting arrows can be styled further by setting the `.Patch`
  3453. properties such as *linewidth*, *color*, *facecolor*, *edgecolor*
  3454. etc. via keyword arguments.
  3455. Parameters
  3456. ----------
  3457. posA, posB : (float, float), optional
  3458. (x, y) coordinates of start and end point of the arrow.
  3459. The actually drawn start and end positions may be modified
  3460. through *patchA*, *patchB*, *shrinkA*, and *shrinkB*.
  3461. *posA*, *posB* are exclusive of *path*.
  3462. path : `~matplotlib.path.Path`, optional
  3463. If provided, an arrow is drawn along this path and *patchA*,
  3464. *patchB*, *shrinkA*, and *shrinkB* are ignored.
  3465. *path* is exclusive of *posA*, *posB*.
  3466. arrowstyle : str or `.ArrowStyle`, default: 'simple'
  3467. The styling of arrow head, tail and shaft. This can be
  3468. - `.ArrowStyle` or one of its subclasses
  3469. - The shorthand string name (e.g. "->") as given in the table below,
  3470. optionally containing a comma-separated list of style parameters,
  3471. e.g. "->, head_length=10, head_width=5".
  3472. The style parameters are scaled by *mutation_scale*.
  3473. The following arrow styles are available. See also
  3474. :doc:`/gallery/text_labels_and_annotations/fancyarrow_demo`.
  3475. %(ArrowStyle:table)s
  3476. Only the styles ``<|-``, ``-|>``, ``<|-|>`` ``simple``, ``fancy``
  3477. and ``wedge`` contain closed paths and can be filled.
  3478. connectionstyle : str or `.ConnectionStyle` or None, optional, \
  3479. default: 'arc3'
  3480. `.ConnectionStyle` with which *posA* and *posB* are connected.
  3481. This can be
  3482. - `.ConnectionStyle` or one of its subclasses
  3483. - The shorthand string name as given in the table below, e.g. "arc3".
  3484. %(ConnectionStyle:table)s
  3485. Ignored if *path* is provided.
  3486. patchA, patchB : `~matplotlib.patches.Patch`, default: None
  3487. Optional Patches at *posA* and *posB*, respectively. If given,
  3488. the arrow path is clipped by these patches such that head and tail
  3489. are at the border of the patches.
  3490. Ignored if *path* is provided.
  3491. shrinkA, shrinkB : float, default: 2
  3492. Shorten the arrow path at *posA* and *posB* by this amount in points.
  3493. This allows to add a margin between the intended start/end points and
  3494. the arrow.
  3495. Ignored if *path* is provided.
  3496. mutation_scale : float, default: 1
  3497. Value with which attributes of *arrowstyle* (e.g., *head_length*)
  3498. will be scaled.
  3499. mutation_aspect : None or float, default: None
  3500. The height of the rectangle will be squeezed by this value before
  3501. the mutation and the mutated box will be stretched by the inverse
  3502. of it.
  3503. Other Parameters
  3504. ----------------
  3505. **kwargs : `~matplotlib.patches.Patch` properties, optional
  3506. Here is a list of available `.Patch` properties:
  3507. %(Patch:kwdoc)s
  3508. In contrast to other patches, the default ``capstyle`` and
  3509. ``joinstyle`` for `FancyArrowPatch` are set to ``"round"``.
  3510. """
  3511. # Traditionally, the cap- and joinstyle for FancyArrowPatch are round
  3512. kwargs.setdefault("joinstyle", JoinStyle.round)
  3513. kwargs.setdefault("capstyle", CapStyle.round)
  3514. super().__init__(**kwargs)
  3515. if posA is not None and posB is not None and path is None:
  3516. self._posA_posB = [posA, posB]
  3517. if connectionstyle is None:
  3518. connectionstyle = "arc3"
  3519. self.set_connectionstyle(connectionstyle)
  3520. elif posA is None and posB is None and path is not None:
  3521. self._posA_posB = None
  3522. else:
  3523. raise ValueError("Either posA and posB, or path need to provided")
  3524. self.patchA = patchA
  3525. self.patchB = patchB
  3526. self.shrinkA = shrinkA
  3527. self.shrinkB = shrinkB
  3528. self._path_original = path
  3529. self.set_arrowstyle(arrowstyle)
  3530. self._mutation_scale = mutation_scale
  3531. self._mutation_aspect = mutation_aspect
  3532. self._dpi_cor = 1.0
  3533. def set_positions(self, posA, posB):
  3534. """
  3535. Set the start and end positions of the connecting path.
  3536. Parameters
  3537. ----------
  3538. posA, posB : None, tuple
  3539. (x, y) coordinates of arrow tail and arrow head respectively. If
  3540. `None` use current value.
  3541. """
  3542. if posA is not None:
  3543. self._posA_posB[0] = posA
  3544. if posB is not None:
  3545. self._posA_posB[1] = posB
  3546. self.stale = True
  3547. def set_patchA(self, patchA):
  3548. """
  3549. Set the tail patch.
  3550. Parameters
  3551. ----------
  3552. patchA : `.patches.Patch`
  3553. """
  3554. self.patchA = patchA
  3555. self.stale = True
  3556. def set_patchB(self, patchB):
  3557. """
  3558. Set the head patch.
  3559. Parameters
  3560. ----------
  3561. patchB : `.patches.Patch`
  3562. """
  3563. self.patchB = patchB
  3564. self.stale = True
  3565. @_docstring.interpd
  3566. def set_connectionstyle(self, connectionstyle=None, **kwargs):
  3567. """
  3568. Set the connection style, possibly with further attributes.
  3569. Attributes from the previous connection style are not reused.
  3570. Without argument (or with ``connectionstyle=None``), the available box
  3571. styles are returned as a human-readable string.
  3572. Parameters
  3573. ----------
  3574. connectionstyle : str or `~matplotlib.patches.ConnectionStyle`
  3575. The style of the connection: either a `.ConnectionStyle` instance,
  3576. or a string, which is the style name and optionally comma separated
  3577. attributes (e.g. "Arc,armA=30,rad=10"). Such a string is used to
  3578. construct a `.ConnectionStyle` object, as documented in that class.
  3579. The following connection styles are available:
  3580. %(ConnectionStyle:table_and_accepts)s
  3581. **kwargs
  3582. Additional attributes for the connection style. See the table above
  3583. for supported parameters.
  3584. Examples
  3585. --------
  3586. ::
  3587. set_connectionstyle("Arc,armA=30,rad=10")
  3588. set_connectionstyle("arc", armA=30, rad=10)
  3589. """
  3590. if connectionstyle is None:
  3591. return ConnectionStyle.pprint_styles()
  3592. self._connector = (
  3593. ConnectionStyle(connectionstyle, **kwargs)
  3594. if isinstance(connectionstyle, str) else connectionstyle)
  3595. self.stale = True
  3596. def get_connectionstyle(self):
  3597. """Return the `ConnectionStyle` used."""
  3598. return self._connector
  3599. @_docstring.interpd
  3600. def set_arrowstyle(self, arrowstyle=None, **kwargs):
  3601. """
  3602. Set the arrow style, possibly with further attributes.
  3603. Attributes from the previous arrow style are not reused.
  3604. Without argument (or with ``arrowstyle=None``), the available box
  3605. styles are returned as a human-readable string.
  3606. Parameters
  3607. ----------
  3608. arrowstyle : str or `~matplotlib.patches.ArrowStyle`
  3609. The style of the arrow: either a `.ArrowStyle` instance, or a
  3610. string, which is the style name and optionally comma separated
  3611. attributes (e.g. "Fancy,head_length=0.2"). Such a string is used to
  3612. construct a `.ArrowStyle` object, as documented in that class.
  3613. The following arrow styles are available:
  3614. %(ArrowStyle:table_and_accepts)s
  3615. **kwargs
  3616. Additional attributes for the arrow style. See the table above for
  3617. supported parameters.
  3618. Examples
  3619. --------
  3620. ::
  3621. set_arrowstyle("Fancy,head_length=0.2")
  3622. set_arrowstyle("fancy", head_length=0.2)
  3623. """
  3624. if arrowstyle is None:
  3625. return ArrowStyle.pprint_styles()
  3626. self._arrow_transmuter = (
  3627. ArrowStyle(arrowstyle, **kwargs)
  3628. if isinstance(arrowstyle, str) else arrowstyle)
  3629. self.stale = True
  3630. def get_arrowstyle(self):
  3631. """Return the arrowstyle object."""
  3632. return self._arrow_transmuter
  3633. def set_mutation_scale(self, scale):
  3634. """
  3635. Set the mutation scale.
  3636. Parameters
  3637. ----------
  3638. scale : float
  3639. """
  3640. self._mutation_scale = scale
  3641. self.stale = True
  3642. def get_mutation_scale(self):
  3643. """
  3644. Return the mutation scale.
  3645. Returns
  3646. -------
  3647. scalar
  3648. """
  3649. return self._mutation_scale
  3650. def set_mutation_aspect(self, aspect):
  3651. """
  3652. Set the aspect ratio of the bbox mutation.
  3653. Parameters
  3654. ----------
  3655. aspect : float
  3656. """
  3657. self._mutation_aspect = aspect
  3658. self.stale = True
  3659. def get_mutation_aspect(self):
  3660. """Return the aspect ratio of the bbox mutation."""
  3661. return (self._mutation_aspect if self._mutation_aspect is not None
  3662. else 1) # backcompat.
  3663. def get_path(self):
  3664. """Return the path of the arrow in the data coordinates."""
  3665. # The path is generated in display coordinates, then converted back to
  3666. # data coordinates.
  3667. _path, fillable = self._get_path_in_displaycoord()
  3668. if np.iterable(fillable):
  3669. _path = Path.make_compound_path(*_path)
  3670. return self.get_transform().inverted().transform_path(_path)
  3671. def _get_path_in_displaycoord(self):
  3672. """Return the mutated path of the arrow in display coordinates."""
  3673. dpi_cor = self._dpi_cor
  3674. if self._posA_posB is not None:
  3675. posA = self._convert_xy_units(self._posA_posB[0])
  3676. posB = self._convert_xy_units(self._posA_posB[1])
  3677. (posA, posB) = self.get_transform().transform((posA, posB))
  3678. _path = self.get_connectionstyle()(posA, posB,
  3679. patchA=self.patchA,
  3680. patchB=self.patchB,
  3681. shrinkA=self.shrinkA * dpi_cor,
  3682. shrinkB=self.shrinkB * dpi_cor
  3683. )
  3684. else:
  3685. _path = self.get_transform().transform_path(self._path_original)
  3686. _path, fillable = self.get_arrowstyle()(
  3687. _path,
  3688. self.get_mutation_scale() * dpi_cor,
  3689. self.get_linewidth() * dpi_cor,
  3690. self.get_mutation_aspect())
  3691. return _path, fillable
  3692. def draw(self, renderer):
  3693. if not self.get_visible():
  3694. return
  3695. # FIXME: dpi_cor is for the dpi-dependency of the linewidth. There
  3696. # could be room for improvement. Maybe _get_path_in_displaycoord could
  3697. # take a renderer argument, but get_path should be adapted too.
  3698. self._dpi_cor = renderer.points_to_pixels(1.)
  3699. path, fillable = self._get_path_in_displaycoord()
  3700. if not np.iterable(fillable):
  3701. path = [path]
  3702. fillable = [fillable]
  3703. affine = transforms.IdentityTransform()
  3704. self._draw_paths_with_artist_properties(
  3705. renderer,
  3706. [(p, affine, self._facecolor if f and self._facecolor[3] else None)
  3707. for p, f in zip(path, fillable)])
  3708. class ConnectionPatch(FancyArrowPatch):
  3709. """A patch that connects two points (possibly in different Axes)."""
  3710. def __str__(self):
  3711. return "ConnectionPatch((%g, %g), (%g, %g))" % \
  3712. (self.xy1[0], self.xy1[1], self.xy2[0], self.xy2[1])
  3713. @_docstring.interpd
  3714. def __init__(self, xyA, xyB, coordsA, coordsB=None, *,
  3715. axesA=None, axesB=None,
  3716. arrowstyle="-",
  3717. connectionstyle="arc3",
  3718. patchA=None,
  3719. patchB=None,
  3720. shrinkA=0.,
  3721. shrinkB=0.,
  3722. mutation_scale=10.,
  3723. mutation_aspect=None,
  3724. clip_on=False,
  3725. **kwargs):
  3726. """
  3727. Connect point *xyA* in *coordsA* with point *xyB* in *coordsB*.
  3728. Valid keys are
  3729. =============== ======================================================
  3730. Key Description
  3731. =============== ======================================================
  3732. arrowstyle the arrow style
  3733. connectionstyle the connection style
  3734. relpos default is (0.5, 0.5)
  3735. patchA default is bounding box of the text
  3736. patchB default is None
  3737. shrinkA default is 2 points
  3738. shrinkB default is 2 points
  3739. mutation_scale default is text size (in points)
  3740. mutation_aspect default is 1.
  3741. ? any key for `matplotlib.patches.PathPatch`
  3742. =============== ======================================================
  3743. *coordsA* and *coordsB* are strings that indicate the
  3744. coordinates of *xyA* and *xyB*.
  3745. ==================== ==================================================
  3746. Property Description
  3747. ==================== ==================================================
  3748. 'figure points' points from the lower left corner of the figure
  3749. 'figure pixels' pixels from the lower left corner of the figure
  3750. 'figure fraction' 0, 0 is lower left of figure and 1, 1 is upper
  3751. right
  3752. 'subfigure points' points from the lower left corner of the subfigure
  3753. 'subfigure pixels' pixels from the lower left corner of the subfigure
  3754. 'subfigure fraction' fraction of the subfigure, 0, 0 is lower left.
  3755. 'axes points' points from lower left corner of the Axes
  3756. 'axes pixels' pixels from lower left corner of the Axes
  3757. 'axes fraction' 0, 0 is lower left of Axes and 1, 1 is upper right
  3758. 'data' use the coordinate system of the object being
  3759. annotated (default)
  3760. 'offset points' offset (in points) from the *xy* value
  3761. 'polar' you can specify *theta*, *r* for the annotation,
  3762. even in cartesian plots. Note that if you are
  3763. using a polar Axes, you do not need to specify
  3764. polar for the coordinate system since that is the
  3765. native "data" coordinate system.
  3766. ==================== ==================================================
  3767. Alternatively they can be set to any valid
  3768. `~matplotlib.transforms.Transform`.
  3769. Note that 'subfigure pixels' and 'figure pixels' are the same
  3770. for the parent figure, so users who want code that is usable in
  3771. a subfigure can use 'subfigure pixels'.
  3772. .. note::
  3773. Using `ConnectionPatch` across two `~.axes.Axes` instances
  3774. is not directly compatible with :ref:`constrained layout
  3775. <constrainedlayout_guide>`. Add the artist
  3776. directly to the `.Figure` instead of adding it to a specific Axes,
  3777. or exclude it from the layout using ``con.set_in_layout(False)``.
  3778. .. code-block:: default
  3779. fig, ax = plt.subplots(1, 2, constrained_layout=True)
  3780. con = ConnectionPatch(..., axesA=ax[0], axesB=ax[1])
  3781. fig.add_artist(con)
  3782. """
  3783. if coordsB is None:
  3784. coordsB = coordsA
  3785. # we'll draw ourself after the artist we annotate by default
  3786. self.xy1 = xyA
  3787. self.xy2 = xyB
  3788. self.coords1 = coordsA
  3789. self.coords2 = coordsB
  3790. self.axesA = axesA
  3791. self.axesB = axesB
  3792. super().__init__(posA=(0, 0), posB=(1, 1),
  3793. arrowstyle=arrowstyle,
  3794. connectionstyle=connectionstyle,
  3795. patchA=patchA, patchB=patchB,
  3796. shrinkA=shrinkA, shrinkB=shrinkB,
  3797. mutation_scale=mutation_scale,
  3798. mutation_aspect=mutation_aspect,
  3799. clip_on=clip_on,
  3800. **kwargs)
  3801. # if True, draw annotation only if self.xy is inside the Axes
  3802. self._annotation_clip = None
  3803. def _get_xy(self, xy, s, axes=None):
  3804. """Calculate the pixel position of given point."""
  3805. s0 = s # For the error message, if needed.
  3806. if axes is None:
  3807. axes = self.axes
  3808. # preserve mixed type input (such as str, int)
  3809. x = np.array(xy[0])
  3810. y = np.array(xy[1])
  3811. fig = self.get_figure(root=False)
  3812. if s in ["figure points", "axes points"]:
  3813. x = x * fig.dpi / 72
  3814. y = y * fig.dpi / 72
  3815. s = s.replace("points", "pixels")
  3816. elif s == "figure fraction":
  3817. s = fig.transFigure
  3818. elif s == "subfigure fraction":
  3819. s = fig.transSubfigure
  3820. elif s == "axes fraction":
  3821. s = axes.transAxes
  3822. if s == 'data':
  3823. trans = axes.transData
  3824. x = cbook._to_unmasked_float_array(axes.xaxis.convert_units(x))
  3825. y = cbook._to_unmasked_float_array(axes.yaxis.convert_units(y))
  3826. return trans.transform((x, y))
  3827. elif s == 'offset points':
  3828. if self.xycoords == 'offset points': # prevent recursion
  3829. return self._get_xy(self.xy, 'data')
  3830. return (
  3831. self._get_xy(self.xy, self.xycoords) # converted data point
  3832. + xy * self.get_figure(root=True).dpi / 72) # converted offset
  3833. elif s == 'polar':
  3834. theta, r = x, y
  3835. x = r * np.cos(theta)
  3836. y = r * np.sin(theta)
  3837. trans = axes.transData
  3838. return trans.transform((x, y))
  3839. elif s == 'figure pixels':
  3840. # pixels from the lower left corner of the figure
  3841. bb = self.get_figure(root=False).figbbox
  3842. x = bb.x0 + x if x >= 0 else bb.x1 + x
  3843. y = bb.y0 + y if y >= 0 else bb.y1 + y
  3844. return x, y
  3845. elif s == 'subfigure pixels':
  3846. # pixels from the lower left corner of the figure
  3847. bb = self.get_figure(root=False).bbox
  3848. x = bb.x0 + x if x >= 0 else bb.x1 + x
  3849. y = bb.y0 + y if y >= 0 else bb.y1 + y
  3850. return x, y
  3851. elif s == 'axes pixels':
  3852. # pixels from the lower left corner of the Axes
  3853. bb = axes.bbox
  3854. x = bb.x0 + x if x >= 0 else bb.x1 + x
  3855. y = bb.y0 + y if y >= 0 else bb.y1 + y
  3856. return x, y
  3857. elif isinstance(s, transforms.Transform):
  3858. return s.transform(xy)
  3859. else:
  3860. raise ValueError(f"{s0} is not a valid coordinate transformation")
  3861. def set_annotation_clip(self, b):
  3862. """
  3863. Set the annotation's clipping behavior.
  3864. Parameters
  3865. ----------
  3866. b : bool or None
  3867. - True: The annotation will be clipped when ``self.xy`` is
  3868. outside the Axes.
  3869. - False: The annotation will always be drawn.
  3870. - None: The annotation will be clipped when ``self.xy`` is
  3871. outside the Axes and ``self.xycoords == "data"``.
  3872. """
  3873. self._annotation_clip = b
  3874. self.stale = True
  3875. def get_annotation_clip(self):
  3876. """
  3877. Return the clipping behavior.
  3878. See `.set_annotation_clip` for the meaning of the return value.
  3879. """
  3880. return self._annotation_clip
  3881. def _get_path_in_displaycoord(self):
  3882. """Return the mutated path of the arrow in display coordinates."""
  3883. dpi_cor = self._dpi_cor
  3884. posA = self._get_xy(self.xy1, self.coords1, self.axesA)
  3885. posB = self._get_xy(self.xy2, self.coords2, self.axesB)
  3886. path = self.get_connectionstyle()(
  3887. posA, posB,
  3888. patchA=self.patchA, patchB=self.patchB,
  3889. shrinkA=self.shrinkA * dpi_cor, shrinkB=self.shrinkB * dpi_cor,
  3890. )
  3891. path, fillable = self.get_arrowstyle()(
  3892. path,
  3893. self.get_mutation_scale() * dpi_cor,
  3894. self.get_linewidth() * dpi_cor,
  3895. self.get_mutation_aspect()
  3896. )
  3897. return path, fillable
  3898. def _check_xy(self, renderer):
  3899. """Check whether the annotation needs to be drawn."""
  3900. b = self.get_annotation_clip()
  3901. if b or (b is None and self.coords1 == "data"):
  3902. xy_pixel = self._get_xy(self.xy1, self.coords1, self.axesA)
  3903. if self.axesA is None:
  3904. axes = self.axes
  3905. else:
  3906. axes = self.axesA
  3907. if not axes.contains_point(xy_pixel):
  3908. return False
  3909. if b or (b is None and self.coords2 == "data"):
  3910. xy_pixel = self._get_xy(self.xy2, self.coords2, self.axesB)
  3911. if self.axesB is None:
  3912. axes = self.axes
  3913. else:
  3914. axes = self.axesB
  3915. if not axes.contains_point(xy_pixel):
  3916. return False
  3917. return True
  3918. def draw(self, renderer):
  3919. if not self.get_visible() or not self._check_xy(renderer):
  3920. return
  3921. super().draw(renderer)