patches.py 145 KB

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