1
0

patches.py 148 KB

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