axes3d.py 103 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958
  1. """
  2. axes3d.py, original mplot3d version by John Porter
  3. Created: 23 Sep 2005
  4. Parts fixed by Reinier Heeres <reinier@heeres.eu>
  5. Minor additions by Ben Axelrod <baxelrod@coroware.com>
  6. Significant updates and revisions by Ben Root <ben.v.root@gmail.com>
  7. Module containing Axes3D, an object which can plot 3D objects on a
  8. 2D matplotlib figure.
  9. """
  10. from __future__ import (absolute_import, division, print_function,
  11. unicode_literals)
  12. import six
  13. from six.moves import map, xrange, zip, reduce
  14. import math
  15. import warnings
  16. from collections import defaultdict
  17. import numpy as np
  18. import matplotlib.axes as maxes
  19. import matplotlib.cbook as cbook
  20. import matplotlib.collections as mcoll
  21. import matplotlib.colors as mcolors
  22. import matplotlib.docstring as docstring
  23. import matplotlib.scale as mscale
  24. import matplotlib.transforms as mtransforms
  25. from matplotlib.axes import Axes, rcParams
  26. from matplotlib.cbook import _backports
  27. from matplotlib.colors import Normalize, LightSource
  28. from matplotlib.transforms import Bbox
  29. from matplotlib.tri.triangulation import Triangulation
  30. from . import art3d
  31. from . import proj3d
  32. from . import axis3d
  33. def unit_bbox():
  34. box = Bbox(np.array([[0, 0], [1, 1]]))
  35. return box
  36. class Axes3D(Axes):
  37. """
  38. 3D axes object.
  39. """
  40. name = '3d'
  41. _shared_z_axes = cbook.Grouper()
  42. def __init__(self, fig, rect=None, *args, **kwargs):
  43. '''
  44. Build an :class:`Axes3D` instance in
  45. :class:`~matplotlib.figure.Figure` *fig* with
  46. *rect=[left, bottom, width, height]* in
  47. :class:`~matplotlib.figure.Figure` coordinates
  48. Optional keyword arguments:
  49. ================ =========================================
  50. Keyword Description
  51. ================ =========================================
  52. *azim* Azimuthal viewing angle (default -60)
  53. *elev* Elevation viewing angle (default 30)
  54. *zscale* [%(scale)s]
  55. *sharez* Other axes to share z-limits with
  56. *proj_type* 'persp' or 'ortho' (default 'persp')
  57. ================ =========================================
  58. .. versionadded :: 1.2.1
  59. *sharez*
  60. ''' % {'scale': ' | '.join([repr(x) for x in mscale.get_scale_names()])}
  61. if rect is None:
  62. rect = [0.0, 0.0, 1.0, 1.0]
  63. self._cids = []
  64. self.initial_azim = kwargs.pop('azim', -60)
  65. self.initial_elev = kwargs.pop('elev', 30)
  66. zscale = kwargs.pop('zscale', None)
  67. sharez = kwargs.pop('sharez', None)
  68. self.set_proj_type(kwargs.pop('proj_type', 'persp'))
  69. self.xy_viewLim = unit_bbox()
  70. self.zz_viewLim = unit_bbox()
  71. self.xy_dataLim = unit_bbox()
  72. self.zz_dataLim = unit_bbox()
  73. # inihibit autoscale_view until the axes are defined
  74. # they can't be defined until Axes.__init__ has been called
  75. self.view_init(self.initial_elev, self.initial_azim)
  76. self._ready = 0
  77. self._sharez = sharez
  78. if sharez is not None:
  79. self._shared_z_axes.join(self, sharez)
  80. self._adjustable = 'datalim'
  81. super(Axes3D, self).__init__(fig, rect,
  82. frameon=True,
  83. *args, **kwargs)
  84. # Disable drawing of axes by base class
  85. super(Axes3D, self).set_axis_off()
  86. # Enable drawing of axes by Axes3D class
  87. self.set_axis_on()
  88. self.M = None
  89. # func used to format z -- fall back on major formatters
  90. self.fmt_zdata = None
  91. if zscale is not None:
  92. self.set_zscale(zscale)
  93. if self.zaxis is not None:
  94. self._zcid = self.zaxis.callbacks.connect(
  95. 'units finalize', lambda: self._on_units_changed(scalez=True))
  96. else:
  97. self._zcid = None
  98. self._ready = 1
  99. self.mouse_init()
  100. self.set_top_view()
  101. self.patch.set_linewidth(0)
  102. # Calculate the pseudo-data width and height
  103. pseudo_bbox = self.transLimits.inverted().transform([(0, 0), (1, 1)])
  104. self._pseudo_w, self._pseudo_h = pseudo_bbox[1] - pseudo_bbox[0]
  105. self.figure.add_axes(self)
  106. def set_axis_off(self):
  107. self._axis3don = False
  108. self.stale = True
  109. def set_axis_on(self):
  110. self._axis3don = True
  111. self.stale = True
  112. def have_units(self):
  113. """
  114. Return *True* if units are set on the *x*, *y*, or *z* axes
  115. """
  116. return (self.xaxis.have_units() or self.yaxis.have_units() or
  117. self.zaxis.have_units())
  118. def convert_zunits(self, z):
  119. """
  120. For artists in an axes, if the zaxis has units support,
  121. convert *z* using zaxis unit type
  122. .. versionadded :: 1.2.1
  123. """
  124. return self.zaxis.convert_units(z)
  125. def _process_unit_info(self, xdata=None, ydata=None, zdata=None,
  126. kwargs=None):
  127. """
  128. Look for unit *kwargs* and update the axis instances as necessary
  129. """
  130. super(Axes3D, self)._process_unit_info(xdata=xdata, ydata=ydata,
  131. kwargs=kwargs)
  132. if self.xaxis is None or self.yaxis is None or self.zaxis is None:
  133. return
  134. if zdata is not None:
  135. # we only need to update if there is nothing set yet.
  136. if not self.zaxis.have_units():
  137. self.zaxis.update_units(xdata)
  138. # process kwargs 2nd since these will override default units
  139. if kwargs is not None:
  140. zunits = kwargs.pop('zunits', self.zaxis.units)
  141. if zunits != self.zaxis.units:
  142. self.zaxis.set_units(zunits)
  143. # If the units being set imply a different converter,
  144. # we need to update.
  145. if zdata is not None:
  146. self.zaxis.update_units(zdata)
  147. def set_top_view(self):
  148. # this happens to be the right view for the viewing coordinates
  149. # moved up and to the left slightly to fit labels and axes
  150. xdwl = (0.95/self.dist)
  151. xdw = (0.9/self.dist)
  152. ydwl = (0.95/self.dist)
  153. ydw = (0.9/self.dist)
  154. # This is purposely using the 2D Axes's set_xlim and set_ylim,
  155. # because we are trying to place our viewing pane.
  156. super(Axes3D, self).set_xlim(-xdwl, xdw, auto=None)
  157. super(Axes3D, self).set_ylim(-ydwl, ydw, auto=None)
  158. def _init_axis(self):
  159. '''Init 3D axes; overrides creation of regular X/Y axes'''
  160. self.w_xaxis = axis3d.XAxis('x', self.xy_viewLim.intervalx,
  161. self.xy_dataLim.intervalx, self)
  162. self.xaxis = self.w_xaxis
  163. self.w_yaxis = axis3d.YAxis('y', self.xy_viewLim.intervaly,
  164. self.xy_dataLim.intervaly, self)
  165. self.yaxis = self.w_yaxis
  166. self.w_zaxis = axis3d.ZAxis('z', self.zz_viewLim.intervalx,
  167. self.zz_dataLim.intervalx, self)
  168. self.zaxis = self.w_zaxis
  169. for ax in self.xaxis, self.yaxis, self.zaxis:
  170. ax.init3d()
  171. def get_children(self):
  172. return [self.zaxis, ] + super(Axes3D, self).get_children()
  173. def _get_axis_list(self):
  174. return super(Axes3D, self)._get_axis_list() + (self.zaxis, )
  175. def unit_cube(self, vals=None):
  176. minx, maxx, miny, maxy, minz, maxz = vals or self.get_w_lims()
  177. xs, ys, zs = ([minx, maxx, maxx, minx, minx, maxx, maxx, minx],
  178. [miny, miny, maxy, maxy, miny, miny, maxy, maxy],
  179. [minz, minz, minz, minz, maxz, maxz, maxz, maxz])
  180. return list(zip(xs, ys, zs))
  181. def tunit_cube(self, vals=None, M=None):
  182. if M is None:
  183. M = self.M
  184. xyzs = self.unit_cube(vals)
  185. tcube = proj3d.proj_points(xyzs, M)
  186. return tcube
  187. def tunit_edges(self, vals=None, M=None):
  188. tc = self.tunit_cube(vals, M)
  189. edges = [(tc[0], tc[1]),
  190. (tc[1], tc[2]),
  191. (tc[2], tc[3]),
  192. (tc[3], tc[0]),
  193. (tc[0], tc[4]),
  194. (tc[1], tc[5]),
  195. (tc[2], tc[6]),
  196. (tc[3], tc[7]),
  197. (tc[4], tc[5]),
  198. (tc[5], tc[6]),
  199. (tc[6], tc[7]),
  200. (tc[7], tc[4])]
  201. return edges
  202. def draw(self, renderer):
  203. # draw the background patch
  204. self.patch.draw(renderer)
  205. self._frameon = False
  206. # first, set the aspect
  207. # this is duplicated from `axes._base._AxesBase.draw`
  208. # but must be called before any of the artist are drawn as
  209. # it adjusts the view limits and the size of the bounding box
  210. # of the axes
  211. locator = self.get_axes_locator()
  212. if locator:
  213. pos = locator(self, renderer)
  214. self.apply_aspect(pos)
  215. else:
  216. self.apply_aspect()
  217. # add the projection matrix to the renderer
  218. self.M = self.get_proj()
  219. renderer.M = self.M
  220. renderer.vvec = self.vvec
  221. renderer.eye = self.eye
  222. renderer.get_axis_position = self.get_axis_position
  223. # Calculate projection of collections and zorder them
  224. for i, col in enumerate(
  225. sorted(self.collections,
  226. key=lambda col: col.do_3d_projection(renderer),
  227. reverse=True)):
  228. col.zorder = i
  229. # Calculate projection of patches and zorder them
  230. for i, patch in enumerate(
  231. sorted(self.patches,
  232. key=lambda patch: patch.do_3d_projection(renderer),
  233. reverse=True)):
  234. patch.zorder = i
  235. if self._axis3don:
  236. axes = (self.xaxis, self.yaxis, self.zaxis)
  237. # Draw panes first
  238. for ax in axes:
  239. ax.draw_pane(renderer)
  240. # Then axes
  241. for ax in axes:
  242. ax.draw(renderer)
  243. # Then rest
  244. super(Axes3D, self).draw(renderer)
  245. def get_axis_position(self):
  246. vals = self.get_w_lims()
  247. tc = self.tunit_cube(vals, self.M)
  248. xhigh = tc[1][2] > tc[2][2]
  249. yhigh = tc[3][2] > tc[2][2]
  250. zhigh = tc[0][2] > tc[2][2]
  251. return xhigh, yhigh, zhigh
  252. def _on_units_changed(self, scalex=False, scaley=False, scalez=False):
  253. """
  254. Callback for processing changes to axis units.
  255. Currently forces updates of data limits and view limits.
  256. """
  257. self.relim()
  258. self.autoscale_view(scalex=scalex, scaley=scaley, scalez=scalez)
  259. def update_datalim(self, xys, **kwargs):
  260. pass
  261. def get_autoscale_on(self):
  262. """
  263. Get whether autoscaling is applied for all axes on plot commands
  264. .. versionadded :: 1.1.0
  265. This function was added, but not tested. Please report any bugs.
  266. """
  267. return super(Axes3D, self).get_autoscale_on() and self.get_autoscalez_on()
  268. def get_autoscalez_on(self):
  269. """
  270. Get whether autoscaling for the z-axis is applied on plot commands
  271. .. versionadded :: 1.1.0
  272. This function was added, but not tested. Please report any bugs.
  273. """
  274. return self._autoscaleZon
  275. def set_autoscale_on(self, b):
  276. """
  277. Set whether autoscaling is applied on plot commands
  278. .. versionadded :: 1.1.0
  279. This function was added, but not tested. Please report any bugs.
  280. Parameters
  281. ----------
  282. b : bool
  283. .. ACCEPTS: bool
  284. """
  285. super(Axes3D, self).set_autoscale_on(b)
  286. self.set_autoscalez_on(b)
  287. def set_autoscalez_on(self, b):
  288. """
  289. Set whether autoscaling for the z-axis is applied on plot commands
  290. .. versionadded :: 1.1.0
  291. This function was added, but not tested. Please report any bugs.
  292. Parameters
  293. ----------
  294. b : bool
  295. .. ACCEPTS: bool
  296. """
  297. self._autoscaleZon = b
  298. def set_zmargin(self, m):
  299. """
  300. Set padding of Z data limits prior to autoscaling.
  301. *m* times the data interval will be added to each
  302. end of that interval before it is used in autoscaling.
  303. accepts: float in range 0 to 1
  304. .. versionadded :: 1.1.0
  305. This function was added, but not tested. Please report any bugs.
  306. """
  307. if m < 0 or m > 1 :
  308. raise ValueError("margin must be in range 0 to 1")
  309. self._zmargin = m
  310. self.stale = True
  311. def margins(self, *args, **kw):
  312. """
  313. Convenience method to set or retrieve autoscaling margins.
  314. signatures::
  315. margins()
  316. returns xmargin, ymargin, zmargin
  317. ::
  318. margins(margin)
  319. margins(xmargin, ymargin, zmargin)
  320. margins(x=xmargin, y=ymargin, z=zmargin)
  321. margins(..., tight=False)
  322. All forms above set the xmargin, ymargin and zmargin
  323. parameters. All keyword parameters are optional. A single argument
  324. specifies xmargin, ymargin and zmargin. The *tight* parameter
  325. is passed to :meth:`autoscale_view`, which is executed after
  326. a margin is changed; the default here is *True*, on the
  327. assumption that when margins are specified, no additional
  328. padding to match tick marks is usually desired. Setting
  329. *tight* to *None* will preserve the previous setting.
  330. Specifying any margin changes only the autoscaling; for example,
  331. if *xmargin* is not None, then *xmargin* times the X data
  332. interval will be added to each end of that interval before
  333. it is used in autoscaling.
  334. .. versionadded :: 1.1.0
  335. This function was added, but not tested. Please report any bugs.
  336. """
  337. if not args and not kw:
  338. return self._xmargin, self._ymargin, self._zmargin
  339. tight = kw.pop('tight', True)
  340. mx = kw.pop('x', None)
  341. my = kw.pop('y', None)
  342. mz = kw.pop('z', None)
  343. if not args:
  344. pass
  345. elif len(args) == 1:
  346. mx = my = mz = args[0]
  347. elif len(args) == 2:
  348. warnings.warn(
  349. "Passing exactly two positional arguments to Axes3D.margins "
  350. "is deprecated. If needed, pass them as keyword arguments "
  351. "instead", cbook.mplDeprecation)
  352. mx, my = args
  353. elif len(args) == 3:
  354. mx, my, mz = args
  355. else:
  356. raise ValueError(
  357. "Axes3D.margins takes at most three positional arguments")
  358. if mx is not None:
  359. self.set_xmargin(mx)
  360. if my is not None:
  361. self.set_ymargin(my)
  362. if mz is not None:
  363. self.set_zmargin(mz)
  364. scalex = mx is not None
  365. scaley = my is not None
  366. scalez = mz is not None
  367. self.autoscale_view(tight=tight, scalex=scalex, scaley=scaley,
  368. scalez=scalez)
  369. def autoscale(self, enable=True, axis='both', tight=None):
  370. """
  371. Convenience method for simple axis view autoscaling.
  372. See :meth:`matplotlib.axes.Axes.autoscale` for full explanation.
  373. Note that this function behaves the same, but for all
  374. three axes. Therefore, 'z' can be passed for *axis*,
  375. and 'both' applies to all three axes.
  376. .. versionadded :: 1.1.0
  377. This function was added, but not tested. Please report any bugs.
  378. """
  379. if enable is None:
  380. scalex = True
  381. scaley = True
  382. scalez = True
  383. else:
  384. if axis in ['x', 'both']:
  385. self._autoscaleXon = scalex = bool(enable)
  386. else:
  387. scalex = False
  388. if axis in ['y', 'both']:
  389. self._autoscaleYon = scaley = bool(enable)
  390. else:
  391. scaley = False
  392. if axis in ['z', 'both']:
  393. self._autoscaleZon = scalez = bool(enable)
  394. else:
  395. scalez = False
  396. self.autoscale_view(tight=tight, scalex=scalex, scaley=scaley,
  397. scalez=scalez)
  398. def auto_scale_xyz(self, X, Y, Z=None, had_data=None):
  399. x, y, z = map(np.asarray, (X, Y, Z))
  400. try:
  401. x, y = x.flatten(), y.flatten()
  402. if Z is not None:
  403. z = z.flatten()
  404. except AttributeError:
  405. raise
  406. # This updates the bounding boxes as to keep a record as
  407. # to what the minimum sized rectangular volume holds the
  408. # data.
  409. self.xy_dataLim.update_from_data_xy(np.array([x, y]).T, not had_data)
  410. if z is not None:
  411. self.zz_dataLim.update_from_data_xy(np.array([z, z]).T, not had_data)
  412. # Let autoscale_view figure out how to use this data.
  413. self.autoscale_view()
  414. def autoscale_view(self, tight=None, scalex=True, scaley=True,
  415. scalez=True):
  416. """
  417. Autoscale the view limits using the data limits.
  418. See :meth:`matplotlib.axes.Axes.autoscale_view` for documentation.
  419. Note that this function applies to the 3D axes, and as such
  420. adds the *scalez* to the function arguments.
  421. .. versionchanged :: 1.1.0
  422. Function signature was changed to better match the 2D version.
  423. *tight* is now explicitly a kwarg and placed first.
  424. .. versionchanged :: 1.2.1
  425. This is now fully functional.
  426. """
  427. if not self._ready:
  428. return
  429. # This method looks at the rectangular volume (see above)
  430. # of data and decides how to scale the view portal to fit it.
  431. if tight is None:
  432. # if image data only just use the datalim
  433. _tight = self._tight or (len(self.images)>0 and
  434. len(self.lines)==0 and
  435. len(self.patches)==0)
  436. else:
  437. _tight = self._tight = bool(tight)
  438. if scalex and self._autoscaleXon:
  439. xshared = self._shared_x_axes.get_siblings(self)
  440. dl = [ax.dataLim for ax in xshared]
  441. bb = mtransforms.BboxBase.union(dl)
  442. x0, x1 = self.xy_dataLim.intervalx
  443. xlocator = self.xaxis.get_major_locator()
  444. try:
  445. x0, x1 = xlocator.nonsingular(x0, x1)
  446. except AttributeError:
  447. x0, x1 = mtransforms.nonsingular(x0, x1, increasing=False,
  448. expander=0.05)
  449. if self._xmargin > 0:
  450. delta = (x1 - x0) * self._xmargin
  451. x0 -= delta
  452. x1 += delta
  453. if not _tight:
  454. x0, x1 = xlocator.view_limits(x0, x1)
  455. self.set_xbound(x0, x1)
  456. if scaley and self._autoscaleYon:
  457. yshared = self._shared_y_axes.get_siblings(self)
  458. dl = [ax.dataLim for ax in yshared]
  459. bb = mtransforms.BboxBase.union(dl)
  460. y0, y1 = self.xy_dataLim.intervaly
  461. ylocator = self.yaxis.get_major_locator()
  462. try:
  463. y0, y1 = ylocator.nonsingular(y0, y1)
  464. except AttributeError:
  465. y0, y1 = mtransforms.nonsingular(y0, y1, increasing=False,
  466. expander=0.05)
  467. if self._ymargin > 0:
  468. delta = (y1 - y0) * self._ymargin
  469. y0 -= delta
  470. y1 += delta
  471. if not _tight:
  472. y0, y1 = ylocator.view_limits(y0, y1)
  473. self.set_ybound(y0, y1)
  474. if scalez and self._autoscaleZon:
  475. zshared = self._shared_z_axes.get_siblings(self)
  476. dl = [ax.dataLim for ax in zshared]
  477. bb = mtransforms.BboxBase.union(dl)
  478. z0, z1 = self.zz_dataLim.intervalx
  479. zlocator = self.zaxis.get_major_locator()
  480. try:
  481. z0, z1 = zlocator.nonsingular(z0, z1)
  482. except AttributeError:
  483. z0, z1 = mtransforms.nonsingular(z0, z1, increasing=False,
  484. expander=0.05)
  485. if self._zmargin > 0:
  486. delta = (z1 - z0) * self._zmargin
  487. z0 -= delta
  488. z1 += delta
  489. if not _tight:
  490. z0, z1 = zlocator.view_limits(z0, z1)
  491. self.set_zbound(z0, z1)
  492. def get_w_lims(self):
  493. '''Get 3D world limits.'''
  494. minx, maxx = self.get_xlim3d()
  495. miny, maxy = self.get_ylim3d()
  496. minz, maxz = self.get_zlim3d()
  497. return minx, maxx, miny, maxy, minz, maxz
  498. def _determine_lims(self, xmin=None, xmax=None, *args, **kwargs):
  499. if xmax is None and cbook.iterable(xmin):
  500. xmin, xmax = xmin
  501. if xmin == xmax:
  502. xmin -= 0.05
  503. xmax += 0.05
  504. return (xmin, xmax)
  505. def set_xlim3d(self, left=None, right=None, emit=True, auto=False, **kw):
  506. """
  507. Set 3D x limits.
  508. See :meth:`matplotlib.axes.Axes.set_xlim` for full documentation.
  509. """
  510. if 'xmin' in kw:
  511. left = kw.pop('xmin')
  512. if 'xmax' in kw:
  513. right = kw.pop('xmax')
  514. if kw:
  515. raise ValueError("unrecognized kwargs: %s" % list(kw))
  516. if right is None and cbook.iterable(left):
  517. left, right = left
  518. self._process_unit_info(xdata=(left, right))
  519. left = self._validate_converted_limits(left, self.convert_xunits)
  520. right = self._validate_converted_limits(right, self.convert_xunits)
  521. old_left, old_right = self.get_xlim()
  522. if left is None:
  523. left = old_left
  524. if right is None:
  525. right = old_right
  526. if left == right:
  527. warnings.warn(('Attempting to set identical left==right results\n'
  528. 'in singular transformations; automatically expanding.\n'
  529. 'left=%s, right=%s') % (left, right))
  530. left, right = mtransforms.nonsingular(left, right, increasing=False)
  531. left, right = self.xaxis.limit_range_for_scale(left, right)
  532. self.xy_viewLim.intervalx = (left, right)
  533. if auto is not None:
  534. self._autoscaleXon = bool(auto)
  535. if emit:
  536. self.callbacks.process('xlim_changed', self)
  537. # Call all of the other x-axes that are shared with this one
  538. for other in self._shared_x_axes.get_siblings(self):
  539. if other is not self:
  540. other.set_xlim(self.xy_viewLim.intervalx,
  541. emit=False, auto=auto)
  542. if (other.figure != self.figure and
  543. other.figure.canvas is not None):
  544. other.figure.canvas.draw_idle()
  545. self.stale = True
  546. return left, right
  547. set_xlim = set_xlim3d
  548. def set_ylim3d(self, bottom=None, top=None, emit=True, auto=False, **kw):
  549. """
  550. Set 3D y limits.
  551. See :meth:`matplotlib.axes.Axes.set_ylim` for full documentation.
  552. """
  553. if 'ymin' in kw:
  554. bottom = kw.pop('ymin')
  555. if 'ymax' in kw:
  556. top = kw.pop('ymax')
  557. if kw:
  558. raise ValueError("unrecognized kwargs: %s" % list(kw))
  559. if top is None and cbook.iterable(bottom):
  560. bottom, top = bottom
  561. self._process_unit_info(ydata=(bottom, top))
  562. bottom = self._validate_converted_limits(bottom, self.convert_yunits)
  563. top = self._validate_converted_limits(top, self.convert_yunits)
  564. old_bottom, old_top = self.get_ylim()
  565. if bottom is None:
  566. bottom = old_bottom
  567. if top is None:
  568. top = old_top
  569. if top == bottom:
  570. warnings.warn(('Attempting to set identical bottom==top results\n'
  571. 'in singular transformations; automatically expanding.\n'
  572. 'bottom=%s, top=%s') % (bottom, top))
  573. bottom, top = mtransforms.nonsingular(bottom, top, increasing=False)
  574. bottom, top = self.yaxis.limit_range_for_scale(bottom, top)
  575. self.xy_viewLim.intervaly = (bottom, top)
  576. if auto is not None:
  577. self._autoscaleYon = bool(auto)
  578. if emit:
  579. self.callbacks.process('ylim_changed', self)
  580. # Call all of the other y-axes that are shared with this one
  581. for other in self._shared_y_axes.get_siblings(self):
  582. if other is not self:
  583. other.set_ylim(self.xy_viewLim.intervaly,
  584. emit=False, auto=auto)
  585. if (other.figure != self.figure and
  586. other.figure.canvas is not None):
  587. other.figure.canvas.draw_idle()
  588. self.stale = True
  589. return bottom, top
  590. set_ylim = set_ylim3d
  591. def set_zlim3d(self, bottom=None, top=None, emit=True, auto=False, **kw):
  592. """
  593. Set 3D z limits.
  594. See :meth:`matplotlib.axes.Axes.set_ylim` for full documentation
  595. """
  596. if 'zmin' in kw:
  597. bottom = kw.pop('zmin')
  598. if 'zmax' in kw:
  599. top = kw.pop('zmax')
  600. if kw:
  601. raise ValueError("unrecognized kwargs: %s" % list(kw))
  602. if top is None and cbook.iterable(bottom):
  603. bottom, top = bottom
  604. self._process_unit_info(zdata=(bottom, top))
  605. bottom = self._validate_converted_limits(bottom, self.convert_zunits)
  606. top = self._validate_converted_limits(top, self.convert_zunits)
  607. old_bottom, old_top = self.get_zlim()
  608. if bottom is None:
  609. bottom = old_bottom
  610. if top is None:
  611. top = old_top
  612. if top == bottom:
  613. warnings.warn(('Attempting to set identical bottom==top results\n'
  614. 'in singular transformations; automatically expanding.\n'
  615. 'bottom=%s, top=%s') % (bottom, top))
  616. bottom, top = mtransforms.nonsingular(bottom, top, increasing=False)
  617. bottom, top = self.zaxis.limit_range_for_scale(bottom, top)
  618. self.zz_viewLim.intervalx = (bottom, top)
  619. if auto is not None:
  620. self._autoscaleZon = bool(auto)
  621. if emit:
  622. self.callbacks.process('zlim_changed', self)
  623. # Call all of the other y-axes that are shared with this one
  624. for other in self._shared_z_axes.get_siblings(self):
  625. if other is not self:
  626. other.set_zlim(self.zz_viewLim.intervalx,
  627. emit=False, auto=auto)
  628. if (other.figure != self.figure and
  629. other.figure.canvas is not None):
  630. other.figure.canvas.draw_idle()
  631. self.stale = True
  632. return bottom, top
  633. set_zlim = set_zlim3d
  634. def get_xlim3d(self):
  635. return tuple(self.xy_viewLim.intervalx)
  636. get_xlim3d.__doc__ = maxes.Axes.get_xlim.__doc__
  637. get_xlim = get_xlim3d
  638. if get_xlim.__doc__ is not None:
  639. get_xlim.__doc__ += """
  640. .. versionchanged :: 1.1.0
  641. This function now correctly refers to the 3D x-limits
  642. """
  643. def get_ylim3d(self):
  644. return tuple(self.xy_viewLim.intervaly)
  645. get_ylim3d.__doc__ = maxes.Axes.get_ylim.__doc__
  646. get_ylim = get_ylim3d
  647. if get_ylim.__doc__ is not None:
  648. get_ylim.__doc__ += """
  649. .. versionchanged :: 1.1.0
  650. This function now correctly refers to the 3D y-limits.
  651. """
  652. def get_zlim3d(self):
  653. '''Get 3D z limits.'''
  654. return tuple(self.zz_viewLim.intervalx)
  655. get_zlim = get_zlim3d
  656. def get_zscale(self):
  657. """
  658. Return the zaxis scale string %s
  659. .. versionadded :: 1.1.0
  660. This function was added, but not tested. Please report any bugs.
  661. """ % (", ".join(mscale.get_scale_names()))
  662. return self.zaxis.get_scale()
  663. # We need to slightly redefine these to pass scalez=False
  664. # to their calls of autoscale_view.
  665. def set_xscale(self, value, **kwargs):
  666. self.xaxis._set_scale(value, **kwargs)
  667. self.autoscale_view(scaley=False, scalez=False)
  668. self._update_transScale()
  669. if maxes.Axes.set_xscale.__doc__ is not None:
  670. set_xscale.__doc__ = maxes.Axes.set_xscale.__doc__ + """
  671. .. versionadded :: 1.1.0
  672. This function was added, but not tested. Please report any bugs.
  673. """
  674. def set_yscale(self, value, **kwargs):
  675. self.yaxis._set_scale(value, **kwargs)
  676. self.autoscale_view(scalex=False, scalez=False)
  677. self._update_transScale()
  678. self.stale = True
  679. if maxes.Axes.set_yscale.__doc__ is not None:
  680. set_yscale.__doc__ = maxes.Axes.set_yscale.__doc__ + """
  681. .. versionadded :: 1.1.0
  682. This function was added, but not tested. Please report any bugs.
  683. """
  684. @docstring.dedent_interpd
  685. def set_zscale(self, value, **kwargs):
  686. """
  687. Set the scaling of the z-axis: %(scale)s
  688. ACCEPTS: [%(scale)s]
  689. Different kwargs are accepted, depending on the scale:
  690. %(scale_docs)s
  691. .. note ::
  692. Currently, Axes3D objects only supports linear scales.
  693. Other scales may or may not work, and support for these
  694. is improving with each release.
  695. .. versionadded :: 1.1.0
  696. This function was added, but not tested. Please report any bugs.
  697. """
  698. self.zaxis._set_scale(value, **kwargs)
  699. self.autoscale_view(scalex=False, scaley=False)
  700. self._update_transScale()
  701. self.stale = True
  702. def set_zticks(self, *args, **kwargs):
  703. """
  704. Set z-axis tick locations.
  705. See :meth:`matplotlib.axes.Axes.set_yticks` for more details.
  706. .. note::
  707. Minor ticks are not supported.
  708. .. versionadded:: 1.1.0
  709. """
  710. return self.zaxis.set_ticks(*args, **kwargs)
  711. def get_zticks(self, minor=False):
  712. """
  713. Return the z ticks as a list of locations
  714. See :meth:`matplotlib.axes.Axes.get_yticks` for more details.
  715. .. note::
  716. Minor ticks are not supported.
  717. .. versionadded:: 1.1.0
  718. """
  719. return self.zaxis.get_ticklocs(minor=minor)
  720. def get_zmajorticklabels(self):
  721. """
  722. Get the ztick labels as a list of Text instances
  723. .. versionadded :: 1.1.0
  724. """
  725. return cbook.silent_list('Text zticklabel',
  726. self.zaxis.get_majorticklabels())
  727. def get_zminorticklabels(self):
  728. """
  729. Get the ztick labels as a list of Text instances
  730. .. note::
  731. Minor ticks are not supported. This function was added
  732. only for completeness.
  733. .. versionadded :: 1.1.0
  734. """
  735. return cbook.silent_list('Text zticklabel',
  736. self.zaxis.get_minorticklabels())
  737. def set_zticklabels(self, *args, **kwargs):
  738. """
  739. Set z-axis tick labels.
  740. See :meth:`matplotlib.axes.Axes.set_yticklabels` for more details.
  741. .. note::
  742. Minor ticks are not supported by Axes3D objects.
  743. .. versionadded:: 1.1.0
  744. """
  745. return self.zaxis.set_ticklabels(*args, **kwargs)
  746. def get_zticklabels(self, minor=False):
  747. """
  748. Get ztick labels as a list of Text instances.
  749. See :meth:`matplotlib.axes.Axes.get_yticklabels` for more details.
  750. .. note::
  751. Minor ticks are not supported.
  752. .. versionadded:: 1.1.0
  753. """
  754. return cbook.silent_list('Text zticklabel',
  755. self.zaxis.get_ticklabels(minor=minor))
  756. def zaxis_date(self, tz=None):
  757. """
  758. Sets up z-axis ticks and labels that treat the z data as dates.
  759. *tz* is a timezone string or :class:`tzinfo` instance.
  760. Defaults to rc value.
  761. .. note::
  762. This function is merely provided for completeness.
  763. Axes3D objects do not officially support dates for ticks,
  764. and so this may or may not work as expected.
  765. .. versionadded :: 1.1.0
  766. This function was added, but not tested. Please report any bugs.
  767. """
  768. self.zaxis.axis_date(tz)
  769. def get_zticklines(self):
  770. """
  771. Get ztick lines as a list of Line2D instances.
  772. Note that this function is provided merely for completeness.
  773. These lines are re-calculated as the display changes.
  774. .. versionadded:: 1.1.0
  775. """
  776. return self.zaxis.get_ticklines()
  777. def clabel(self, *args, **kwargs):
  778. """
  779. This function is currently not implemented for 3D axes.
  780. Returns *None*.
  781. """
  782. return None
  783. def view_init(self, elev=None, azim=None):
  784. """
  785. Set the elevation and azimuth of the axes.
  786. This can be used to rotate the axes programmatically.
  787. 'elev' stores the elevation angle in the z plane.
  788. 'azim' stores the azimuth angle in the x,y plane.
  789. if elev or azim are None (default), then the initial value
  790. is used which was specified in the :class:`Axes3D` constructor.
  791. """
  792. self.dist = 10
  793. if elev is None:
  794. self.elev = self.initial_elev
  795. else:
  796. self.elev = elev
  797. if azim is None:
  798. self.azim = self.initial_azim
  799. else:
  800. self.azim = azim
  801. def set_proj_type(self, proj_type):
  802. """
  803. Set the projection type.
  804. Parameters
  805. ----------
  806. proj_type : str
  807. Type of projection, accepts 'persp' and 'ortho'.
  808. """
  809. if proj_type == 'persp':
  810. self._projection = proj3d.persp_transformation
  811. elif proj_type == 'ortho':
  812. self._projection = proj3d.ortho_transformation
  813. else:
  814. raise ValueError("unrecognized projection: %s" % proj_type)
  815. def get_proj(self):
  816. """
  817. Create the projection matrix from the current viewing position.
  818. elev stores the elevation angle in the z plane
  819. azim stores the azimuth angle in the x,y plane
  820. dist is the distance of the eye viewing point from the object
  821. point.
  822. """
  823. relev, razim = np.pi * self.elev/180, np.pi * self.azim/180
  824. xmin, xmax = self.get_xlim3d()
  825. ymin, ymax = self.get_ylim3d()
  826. zmin, zmax = self.get_zlim3d()
  827. # transform to uniform world coordinates 0-1.0,0-1.0,0-1.0
  828. worldM = proj3d.world_transformation(xmin, xmax,
  829. ymin, ymax,
  830. zmin, zmax)
  831. # look into the middle of the new coordinates
  832. R = np.array([0.5, 0.5, 0.5])
  833. xp = R[0] + np.cos(razim) * np.cos(relev) * self.dist
  834. yp = R[1] + np.sin(razim) * np.cos(relev) * self.dist
  835. zp = R[2] + np.sin(relev) * self.dist
  836. E = np.array((xp, yp, zp))
  837. self.eye = E
  838. self.vvec = R - E
  839. self.vvec = self.vvec / proj3d.mod(self.vvec)
  840. if abs(relev) > np.pi/2:
  841. # upside down
  842. V = np.array((0, 0, -1))
  843. else:
  844. V = np.array((0, 0, 1))
  845. zfront, zback = -self.dist, self.dist
  846. viewM = proj3d.view_transformation(E, R, V)
  847. projM = self._projection(zfront, zback)
  848. M0 = np.dot(viewM, worldM)
  849. M = np.dot(projM, M0)
  850. return M
  851. def mouse_init(self, rotate_btn=1, zoom_btn=3):
  852. """Initializes mouse button callbacks to enable 3D rotation of
  853. the axes. Also optionally sets the mouse buttons for 3D rotation
  854. and zooming.
  855. ============ =======================================================
  856. Argument Description
  857. ============ =======================================================
  858. *rotate_btn* The integer or list of integers specifying which mouse
  859. button or buttons to use for 3D rotation of the axes.
  860. Default = 1.
  861. *zoom_btn* The integer or list of integers specifying which mouse
  862. button or buttons to use to zoom the 3D axes.
  863. Default = 3.
  864. ============ =======================================================
  865. """
  866. self.button_pressed = None
  867. canv = self.figure.canvas
  868. if canv is not None:
  869. c1 = canv.mpl_connect('motion_notify_event', self._on_move)
  870. c2 = canv.mpl_connect('button_press_event', self._button_press)
  871. c3 = canv.mpl_connect('button_release_event', self._button_release)
  872. self._cids = [c1, c2, c3]
  873. else:
  874. warnings.warn(
  875. "Axes3D.figure.canvas is 'None', mouse rotation disabled. "
  876. "Set canvas then call Axes3D.mouse_init().")
  877. # coerce scalars into array-like, then convert into
  878. # a regular list to avoid comparisons against None
  879. # which breaks in recent versions of numpy.
  880. self._rotate_btn = np.atleast_1d(rotate_btn).tolist()
  881. self._zoom_btn = np.atleast_1d(zoom_btn).tolist()
  882. def can_zoom(self):
  883. """
  884. Return *True* if this axes supports the zoom box button functionality.
  885. 3D axes objects do not use the zoom box button.
  886. """
  887. return False
  888. def can_pan(self):
  889. """
  890. Return *True* if this axes supports the pan/zoom button functionality.
  891. 3D axes objects do not use the pan/zoom button.
  892. """
  893. return False
  894. def cla(self):
  895. """
  896. Clear axes
  897. """
  898. # Disabling mouse interaction might have been needed a long
  899. # time ago, but I can't find a reason for it now - BVR (2012-03)
  900. #self.disable_mouse_rotation()
  901. super(Axes3D, self).cla()
  902. self.zaxis.cla()
  903. if self._sharez is not None:
  904. self.zaxis.major = self._sharez.zaxis.major
  905. self.zaxis.minor = self._sharez.zaxis.minor
  906. z0, z1 = self._sharez.get_zlim()
  907. self.set_zlim(z0, z1, emit=False, auto=None)
  908. self.zaxis._set_scale(self._sharez.zaxis.get_scale())
  909. else:
  910. self.zaxis._set_scale('linear')
  911. try:
  912. self.set_zlim(0, 1)
  913. except TypeError:
  914. pass
  915. self._autoscaleZon = True
  916. self._zmargin = 0
  917. self.grid(rcParams['axes3d.grid'])
  918. def disable_mouse_rotation(self):
  919. """Disable mouse button callbacks.
  920. """
  921. # Disconnect the various events we set.
  922. for cid in self._cids:
  923. self.figure.canvas.mpl_disconnect(cid)
  924. self._cids = []
  925. def _button_press(self, event):
  926. if event.inaxes == self:
  927. self.button_pressed = event.button
  928. self.sx, self.sy = event.xdata, event.ydata
  929. def _button_release(self, event):
  930. self.button_pressed = None
  931. def format_zdata(self, z):
  932. """
  933. Return *z* string formatted. This function will use the
  934. :attr:`fmt_zdata` attribute if it is callable, else will fall
  935. back on the zaxis major formatter
  936. """
  937. try: return self.fmt_zdata(z)
  938. except (AttributeError, TypeError):
  939. func = self.zaxis.get_major_formatter().format_data_short
  940. val = func(z)
  941. return val
  942. def format_coord(self, xd, yd):
  943. """
  944. Given the 2D view coordinates attempt to guess a 3D coordinate.
  945. Looks for the nearest edge to the point and then assumes that
  946. the point is at the same z location as the nearest point on the edge.
  947. """
  948. if self.M is None:
  949. return ''
  950. if self.button_pressed in self._rotate_btn:
  951. return 'azimuth=%d deg, elevation=%d deg ' % (self.azim, self.elev)
  952. # ignore xd and yd and display angles instead
  953. # nearest edge
  954. p0, p1 = min(self.tunit_edges(),
  955. key=lambda edge: proj3d.line2d_seg_dist(
  956. edge[0], edge[1], (xd, yd)))
  957. # scale the z value to match
  958. x0, y0, z0 = p0
  959. x1, y1, z1 = p1
  960. d0 = np.hypot(x0-xd, y0-yd)
  961. d1 = np.hypot(x1-xd, y1-yd)
  962. dt = d0+d1
  963. z = d1/dt * z0 + d0/dt * z1
  964. x, y, z = proj3d.inv_transform(xd, yd, z, self.M)
  965. xs = self.format_xdata(x)
  966. ys = self.format_ydata(y)
  967. zs = self.format_zdata(z)
  968. return 'x=%s, y=%s, z=%s' % (xs, ys, zs)
  969. def _on_move(self, event):
  970. """Mouse moving
  971. button-1 rotates by default. Can be set explicitly in mouse_init().
  972. button-3 zooms by default. Can be set explicitly in mouse_init().
  973. """
  974. if not self.button_pressed:
  975. return
  976. if self.M is None:
  977. return
  978. x, y = event.xdata, event.ydata
  979. # In case the mouse is out of bounds.
  980. if x is None:
  981. return
  982. dx, dy = x - self.sx, y - self.sy
  983. w = self._pseudo_w
  984. h = self._pseudo_h
  985. self.sx, self.sy = x, y
  986. # Rotation
  987. if self.button_pressed in self._rotate_btn:
  988. # rotate viewing point
  989. # get the x and y pixel coords
  990. if dx == 0 and dy == 0:
  991. return
  992. self.elev = art3d.norm_angle(self.elev - (dy/h)*180)
  993. self.azim = art3d.norm_angle(self.azim - (dx/w)*180)
  994. self.get_proj()
  995. self.stale = True
  996. self.figure.canvas.draw_idle()
  997. # elif self.button_pressed == 2:
  998. # pan view
  999. # project xv,yv,zv -> xw,yw,zw
  1000. # pan
  1001. # pass
  1002. # Zoom
  1003. elif self.button_pressed in self._zoom_btn:
  1004. # zoom view
  1005. # hmmm..this needs some help from clipping....
  1006. minx, maxx, miny, maxy, minz, maxz = self.get_w_lims()
  1007. df = 1-((h - dy)/h)
  1008. dx = (maxx-minx)*df
  1009. dy = (maxy-miny)*df
  1010. dz = (maxz-minz)*df
  1011. self.set_xlim3d(minx - dx, maxx + dx)
  1012. self.set_ylim3d(miny - dy, maxy + dy)
  1013. self.set_zlim3d(minz - dz, maxz + dz)
  1014. self.get_proj()
  1015. self.figure.canvas.draw_idle()
  1016. def set_zlabel(self, zlabel, fontdict=None, labelpad=None, **kwargs):
  1017. '''
  1018. Set zlabel. See doc for :meth:`set_ylabel` for description.
  1019. '''
  1020. if labelpad is not None : self.zaxis.labelpad = labelpad
  1021. return self.zaxis.set_label_text(zlabel, fontdict, **kwargs)
  1022. def get_zlabel(self):
  1023. """
  1024. Get the z-label text string.
  1025. .. versionadded :: 1.1.0
  1026. This function was added, but not tested. Please report any bugs.
  1027. """
  1028. label = self.zaxis.get_label()
  1029. return label.get_text()
  1030. #### Axes rectangle characteristics
  1031. def get_frame_on(self):
  1032. """
  1033. Get whether the 3D axes panels are drawn.
  1034. .. versionadded :: 1.1.0
  1035. """
  1036. return self._frameon
  1037. def set_frame_on(self, b):
  1038. """
  1039. Set whether the 3D axes panels are drawn.
  1040. .. versionadded :: 1.1.0
  1041. Parameters
  1042. ----------
  1043. b : bool
  1044. .. ACCEPTS: bool
  1045. """
  1046. self._frameon = bool(b)
  1047. self.stale = True
  1048. def get_axisbelow(self):
  1049. """
  1050. Get whether axis below is true or not.
  1051. For axes3d objects, this will always be *True*
  1052. .. versionadded :: 1.1.0
  1053. This function was added for completeness.
  1054. """
  1055. return True
  1056. def set_axisbelow(self, b):
  1057. """
  1058. Set whether axis ticks and gridlines are above or below most artists.
  1059. For axes3d objects, this will ignore any settings and just use *True*
  1060. .. versionadded :: 1.1.0
  1061. This function was added for completeness.
  1062. Parameters
  1063. ----------
  1064. b : bool
  1065. .. ACCEPTS: bool
  1066. """
  1067. self._axisbelow = True
  1068. self.stale = True
  1069. def grid(self, b=True, **kwargs):
  1070. '''
  1071. Set / unset 3D grid.
  1072. .. note::
  1073. Currently, this function does not behave the same as
  1074. :meth:`matplotlib.axes.Axes.grid`, but it is intended to
  1075. eventually support that behavior.
  1076. .. versionchanged :: 1.1.0
  1077. This function was changed, but not tested. Please report any bugs.
  1078. '''
  1079. # TODO: Operate on each axes separately
  1080. if len(kwargs):
  1081. b = True
  1082. self._draw_grid = cbook._string_to_bool(b)
  1083. self.stale = True
  1084. def ticklabel_format(self, **kwargs):
  1085. """
  1086. Convenience method for manipulating the ScalarFormatter
  1087. used by default for linear axes in Axed3D objects.
  1088. See :meth:`matplotlib.axes.Axes.ticklabel_format` for full
  1089. documentation. Note that this version applies to all three
  1090. axes of the Axes3D object. Therefore, the *axis* argument
  1091. will also accept a value of 'z' and the value of 'both' will
  1092. apply to all three axes.
  1093. .. versionadded :: 1.1.0
  1094. This function was added, but not tested. Please report any bugs.
  1095. """
  1096. style = kwargs.pop('style', '').lower()
  1097. scilimits = kwargs.pop('scilimits', None)
  1098. useOffset = kwargs.pop('useOffset', None)
  1099. axis = kwargs.pop('axis', 'both').lower()
  1100. if scilimits is not None:
  1101. try:
  1102. m, n = scilimits
  1103. m+n+1 # check that both are numbers
  1104. except (ValueError, TypeError):
  1105. raise ValueError("scilimits must be a sequence of 2 integers")
  1106. if style[:3] == 'sci':
  1107. sb = True
  1108. elif style in ['plain', 'comma']:
  1109. sb = False
  1110. if style == 'plain':
  1111. cb = False
  1112. else:
  1113. cb = True
  1114. raise NotImplementedError("comma style remains to be added")
  1115. elif style == '':
  1116. sb = None
  1117. else:
  1118. raise ValueError("%s is not a valid style value")
  1119. try:
  1120. if sb is not None:
  1121. if axis in ['both', 'z']:
  1122. self.xaxis.major.formatter.set_scientific(sb)
  1123. if axis in ['both', 'y']:
  1124. self.yaxis.major.formatter.set_scientific(sb)
  1125. if axis in ['both', 'z'] :
  1126. self.zaxis.major.formatter.set_scientific(sb)
  1127. if scilimits is not None:
  1128. if axis in ['both', 'x']:
  1129. self.xaxis.major.formatter.set_powerlimits(scilimits)
  1130. if axis in ['both', 'y']:
  1131. self.yaxis.major.formatter.set_powerlimits(scilimits)
  1132. if axis in ['both', 'z']:
  1133. self.zaxis.major.formatter.set_powerlimits(scilimits)
  1134. if useOffset is not None:
  1135. if axis in ['both', 'x']:
  1136. self.xaxis.major.formatter.set_useOffset(useOffset)
  1137. if axis in ['both', 'y']:
  1138. self.yaxis.major.formatter.set_useOffset(useOffset)
  1139. if axis in ['both', 'z']:
  1140. self.zaxis.major.formatter.set_useOffset(useOffset)
  1141. except AttributeError:
  1142. raise AttributeError(
  1143. "This method only works with the ScalarFormatter.")
  1144. def locator_params(self, axis='both', tight=None, **kwargs):
  1145. """
  1146. Convenience method for controlling tick locators.
  1147. See :meth:`matplotlib.axes.Axes.locator_params` for full
  1148. documentation Note that this is for Axes3D objects,
  1149. therefore, setting *axis* to 'both' will result in the
  1150. parameters being set for all three axes. Also, *axis*
  1151. can also take a value of 'z' to apply parameters to the
  1152. z axis.
  1153. .. versionadded :: 1.1.0
  1154. This function was added, but not tested. Please report any bugs.
  1155. """
  1156. _x = axis in ['x', 'both']
  1157. _y = axis in ['y', 'both']
  1158. _z = axis in ['z', 'both']
  1159. if _x:
  1160. self.xaxis.get_major_locator().set_params(**kwargs)
  1161. if _y:
  1162. self.yaxis.get_major_locator().set_params(**kwargs)
  1163. if _z:
  1164. self.zaxis.get_major_locator().set_params(**kwargs)
  1165. self.autoscale_view(tight=tight, scalex=_x, scaley=_y, scalez=_z)
  1166. def tick_params(self, axis='both', **kwargs):
  1167. """
  1168. Convenience method for changing the appearance of ticks and
  1169. tick labels.
  1170. See :meth:`matplotlib.axes.Axes.tick_params` for more complete
  1171. documentation.
  1172. The only difference is that setting *axis* to 'both' will
  1173. mean that the settings are applied to all three axes. Also,
  1174. the *axis* parameter also accepts a value of 'z', which
  1175. would mean to apply to only the z-axis.
  1176. Also, because of how Axes3D objects are drawn very differently
  1177. from regular 2D axes, some of these settings may have
  1178. ambiguous meaning. For simplicity, the 'z' axis will
  1179. accept settings as if it was like the 'y' axis.
  1180. .. note::
  1181. While this function is currently implemented, the core part
  1182. of the Axes3D object may ignore some of these settings.
  1183. Future releases will fix this. Priority will be given to
  1184. those who file bugs.
  1185. .. versionadded :: 1.1.0
  1186. This function was added, but not tested. Please report any bugs.
  1187. """
  1188. super(Axes3D, self).tick_params(axis, **kwargs)
  1189. if axis in ['z', 'both'] :
  1190. zkw = dict(kwargs)
  1191. zkw.pop('top', None)
  1192. zkw.pop('bottom', None)
  1193. zkw.pop('labeltop', None)
  1194. zkw.pop('labelbottom', None)
  1195. self.zaxis.set_tick_params(**zkw)
  1196. ### data limits, ticks, tick labels, and formatting
  1197. def invert_zaxis(self):
  1198. """
  1199. Invert the z-axis.
  1200. .. versionadded :: 1.1.0
  1201. This function was added, but not tested. Please report any bugs.
  1202. """
  1203. bottom, top = self.get_zlim()
  1204. self.set_zlim(top, bottom, auto=None)
  1205. def zaxis_inverted(self):
  1206. '''
  1207. Returns True if the z-axis is inverted.
  1208. .. versionadded :: 1.1.0
  1209. This function was added, but not tested. Please report any bugs.
  1210. '''
  1211. bottom, top = self.get_zlim()
  1212. return top < bottom
  1213. def get_zbound(self):
  1214. """
  1215. Returns the z-axis numerical bounds where::
  1216. lowerBound < upperBound
  1217. .. versionadded :: 1.1.0
  1218. This function was added, but not tested. Please report any bugs.
  1219. """
  1220. bottom, top = self.get_zlim()
  1221. if bottom < top:
  1222. return bottom, top
  1223. else:
  1224. return top, bottom
  1225. def set_zbound(self, lower=None, upper=None):
  1226. """
  1227. Set the lower and upper numerical bounds of the z-axis.
  1228. This method will honor axes inversion regardless of parameter order.
  1229. It will not change the :attr:`_autoscaleZon` attribute.
  1230. .. versionadded :: 1.1.0
  1231. This function was added, but not tested. Please report any bugs.
  1232. """
  1233. if upper is None and cbook.iterable(lower):
  1234. lower,upper = lower
  1235. old_lower,old_upper = self.get_zbound()
  1236. if lower is None: lower = old_lower
  1237. if upper is None: upper = old_upper
  1238. if self.zaxis_inverted():
  1239. if lower < upper:
  1240. self.set_zlim(upper, lower, auto=None)
  1241. else:
  1242. self.set_zlim(lower, upper, auto=None)
  1243. else :
  1244. if lower < upper:
  1245. self.set_zlim(lower, upper, auto=None)
  1246. else :
  1247. self.set_zlim(upper, lower, auto=None)
  1248. def text(self, x, y, z, s, zdir=None, **kwargs):
  1249. '''
  1250. Add text to the plot. kwargs will be passed on to Axes.text,
  1251. except for the `zdir` keyword, which sets the direction to be
  1252. used as the z direction.
  1253. '''
  1254. text = super(Axes3D, self).text(x, y, s, **kwargs)
  1255. art3d.text_2d_to_3d(text, z, zdir)
  1256. return text
  1257. text3D = text
  1258. text2D = Axes.text
  1259. def plot(self, xs, ys, *args, **kwargs):
  1260. '''
  1261. Plot 2D or 3D data.
  1262. ========== ================================================
  1263. Argument Description
  1264. ========== ================================================
  1265. *xs*, *ys* x, y coordinates of vertices
  1266. *zs* z value(s), either one for all points or one for
  1267. each point.
  1268. *zdir* Which direction to use as z ('x', 'y' or 'z')
  1269. when plotting a 2D set.
  1270. ========== ================================================
  1271. Other arguments are passed on to
  1272. :func:`~matplotlib.axes.Axes.plot`
  1273. '''
  1274. had_data = self.has_data()
  1275. # `zs` can be passed positionally or as keyword; checking whether
  1276. # args[0] is a string matches the behavior of 2D `plot` (via
  1277. # `_process_plot_var_args`).
  1278. if args and not isinstance(args[0], six.string_types):
  1279. zs = args[0]
  1280. args = args[1:]
  1281. if 'zs' in kwargs:
  1282. raise TypeError("plot() for multiple values for argument 'z'")
  1283. else:
  1284. zs = kwargs.pop('zs', 0)
  1285. zdir = kwargs.pop('zdir', 'z')
  1286. # Match length
  1287. zs = _backports.broadcast_to(zs, len(xs))
  1288. lines = super(Axes3D, self).plot(xs, ys, *args, **kwargs)
  1289. for line in lines:
  1290. art3d.line_2d_to_3d(line, zs=zs, zdir=zdir)
  1291. xs, ys, zs = art3d.juggle_axes(xs, ys, zs, zdir)
  1292. self.auto_scale_xyz(xs, ys, zs, had_data)
  1293. return lines
  1294. plot3D = plot
  1295. def plot_surface(self, X, Y, Z, *args, **kwargs):
  1296. """
  1297. Create a surface plot.
  1298. By default it will be colored in shades of a solid color, but it also
  1299. supports color mapping by supplying the *cmap* argument.
  1300. .. note::
  1301. The *rcount* and *ccount* kwargs, which both default to 50,
  1302. determine the maximum number of samples used in each direction. If
  1303. the input data is larger, it will be downsampled (by slicing) to
  1304. these numbers of points.
  1305. Parameters
  1306. ----------
  1307. X, Y, Z : 2d arrays
  1308. Data values.
  1309. rcount, ccount : int
  1310. Maximum number of samples used in each direction. If the input
  1311. data is larger, it will be downsampled (by slicing) to these
  1312. numbers of points. Defaults to 50.
  1313. .. versionadded:: 2.0
  1314. rstride, cstride : int
  1315. Downsampling stride in each direction. These arguments are
  1316. mutually exclusive with *rcount* and *ccount*. If only one of
  1317. *rstride* or *cstride* is set, the other defaults to 10.
  1318. 'classic' mode uses a default of ``rstride = cstride = 10`` instead
  1319. of the new default of ``rcount = ccount = 50``.
  1320. color : color-like
  1321. Color of the surface patches.
  1322. cmap : Colormap
  1323. Colormap of the surface patches.
  1324. facecolors : array-like of colors.
  1325. Colors of each individual patch.
  1326. norm : Normalize
  1327. Normalization for the colormap.
  1328. vmin, vmax : float
  1329. Bounds for the normalization.
  1330. shade : bool
  1331. Whether to shade the face colors.
  1332. **kwargs :
  1333. Other arguments are forwarded to `.Poly3DCollection`.
  1334. """
  1335. had_data = self.has_data()
  1336. if Z.ndim != 2:
  1337. raise ValueError("Argument Z must be 2-dimensional.")
  1338. # TODO: Support masked arrays
  1339. X, Y, Z = np.broadcast_arrays(X, Y, Z)
  1340. rows, cols = Z.shape
  1341. has_stride = 'rstride' in kwargs or 'cstride' in kwargs
  1342. has_count = 'rcount' in kwargs or 'ccount' in kwargs
  1343. if has_stride and has_count:
  1344. raise ValueError("Cannot specify both stride and count arguments")
  1345. rstride = kwargs.pop('rstride', 10)
  1346. cstride = kwargs.pop('cstride', 10)
  1347. rcount = kwargs.pop('rcount', 50)
  1348. ccount = kwargs.pop('ccount', 50)
  1349. if rcParams['_internal.classic_mode']:
  1350. # Strides have priority over counts in classic mode.
  1351. # So, only compute strides from counts
  1352. # if counts were explicitly given
  1353. if has_count:
  1354. rstride = int(max(np.ceil(rows / rcount), 1))
  1355. cstride = int(max(np.ceil(cols / ccount), 1))
  1356. else:
  1357. # If the strides are provided then it has priority.
  1358. # Otherwise, compute the strides from the counts.
  1359. if not has_stride:
  1360. rstride = int(max(np.ceil(rows / rcount), 1))
  1361. cstride = int(max(np.ceil(cols / ccount), 1))
  1362. if 'facecolors' in kwargs:
  1363. fcolors = kwargs.pop('facecolors')
  1364. else:
  1365. color = kwargs.pop('color', None)
  1366. if color is None:
  1367. color = self._get_lines.get_next_color()
  1368. color = np.array(mcolors.to_rgba(color))
  1369. fcolors = None
  1370. cmap = kwargs.get('cmap', None)
  1371. norm = kwargs.pop('norm', None)
  1372. vmin = kwargs.pop('vmin', None)
  1373. vmax = kwargs.pop('vmax', None)
  1374. linewidth = kwargs.get('linewidth', None)
  1375. shade = kwargs.pop('shade', cmap is None)
  1376. lightsource = kwargs.pop('lightsource', None)
  1377. # Shade the data
  1378. if shade and cmap is not None and fcolors is not None:
  1379. fcolors = self._shade_colors_lightsource(Z, cmap, lightsource)
  1380. polys = []
  1381. # Only need these vectors to shade if there is no cmap
  1382. if cmap is None and shade :
  1383. totpts = int(np.ceil((rows - 1) / rstride) *
  1384. np.ceil((cols - 1) / cstride))
  1385. v1 = np.empty((totpts, 3))
  1386. v2 = np.empty((totpts, 3))
  1387. # This indexes the vertex points
  1388. which_pt = 0
  1389. #colset contains the data for coloring: either average z or the facecolor
  1390. colset = []
  1391. for rs in xrange(0, rows-1, rstride):
  1392. for cs in xrange(0, cols-1, cstride):
  1393. ps = []
  1394. for a in (X, Y, Z):
  1395. ztop = a[rs,cs:min(cols, cs+cstride+1)]
  1396. zleft = a[rs+1:min(rows, rs+rstride+1),
  1397. min(cols-1, cs+cstride)]
  1398. zbase = a[min(rows-1, rs+rstride), cs:min(cols, cs+cstride+1):][::-1]
  1399. zright = a[rs:min(rows-1, rs+rstride):, cs][::-1]
  1400. z = np.concatenate((ztop, zleft, zbase, zright))
  1401. ps.append(z)
  1402. # The construction leaves the array with duplicate points, which
  1403. # are removed here.
  1404. ps = list(zip(*ps))
  1405. lastp = np.array([])
  1406. ps2 = [ps[0]] + [ps[i] for i in xrange(1, len(ps)) if ps[i] != ps[i-1]]
  1407. avgzsum = sum(p[2] for p in ps2)
  1408. polys.append(ps2)
  1409. if fcolors is not None:
  1410. colset.append(fcolors[rs][cs])
  1411. else:
  1412. colset.append(avgzsum / len(ps2))
  1413. # Only need vectors to shade if no cmap
  1414. if cmap is None and shade:
  1415. i1, i2, i3 = 0, int(len(ps2)/3), int(2*len(ps2)/3)
  1416. v1[which_pt] = np.array(ps2[i1]) - np.array(ps2[i2])
  1417. v2[which_pt] = np.array(ps2[i2]) - np.array(ps2[i3])
  1418. which_pt += 1
  1419. if cmap is None and shade:
  1420. normals = np.cross(v1, v2)
  1421. else :
  1422. normals = []
  1423. polyc = art3d.Poly3DCollection(polys, *args, **kwargs)
  1424. if fcolors is not None:
  1425. if shade:
  1426. colset = self._shade_colors(colset, normals)
  1427. polyc.set_facecolors(colset)
  1428. polyc.set_edgecolors(colset)
  1429. elif cmap:
  1430. colset = np.array(colset)
  1431. polyc.set_array(colset)
  1432. if vmin is not None or vmax is not None:
  1433. polyc.set_clim(vmin, vmax)
  1434. if norm is not None:
  1435. polyc.set_norm(norm)
  1436. else:
  1437. if shade:
  1438. colset = self._shade_colors(color, normals)
  1439. else:
  1440. colset = color
  1441. polyc.set_facecolors(colset)
  1442. self.add_collection(polyc)
  1443. self.auto_scale_xyz(X, Y, Z, had_data)
  1444. return polyc
  1445. def _generate_normals(self, polygons):
  1446. '''
  1447. Generate normals for polygons by using the first three points.
  1448. This normal of course might not make sense for polygons with
  1449. more than three points not lying in a plane.
  1450. '''
  1451. normals = []
  1452. for verts in polygons:
  1453. v1 = np.array(verts[0]) - np.array(verts[1])
  1454. v2 = np.array(verts[2]) - np.array(verts[0])
  1455. normals.append(np.cross(v1, v2))
  1456. return normals
  1457. def _shade_colors(self, color, normals):
  1458. '''
  1459. Shade *color* using normal vectors given by *normals*.
  1460. *color* can also be an array of the same length as *normals*.
  1461. '''
  1462. shade = np.array([np.dot(n / proj3d.mod(n), [-1, -1, 0.5])
  1463. if proj3d.mod(n) else np.nan
  1464. for n in normals])
  1465. mask = ~np.isnan(shade)
  1466. if len(shade[mask]) > 0:
  1467. norm = Normalize(min(shade[mask]), max(shade[mask]))
  1468. shade[~mask] = min(shade[mask])
  1469. color = mcolors.to_rgba_array(color)
  1470. # shape of color should be (M, 4) (where M is number of faces)
  1471. # shape of shade should be (M,)
  1472. # colors should have final shape of (M, 4)
  1473. alpha = color[:, 3]
  1474. colors = (0.5 + norm(shade)[:, np.newaxis] * 0.5) * color
  1475. colors[:, 3] = alpha
  1476. else:
  1477. colors = np.asanyarray(color).copy()
  1478. return colors
  1479. def _shade_colors_lightsource(self, data, cmap, lightsource):
  1480. if lightsource is None:
  1481. lightsource = LightSource(azdeg=135, altdeg=55)
  1482. return lightsource.shade(data, cmap)
  1483. def plot_wireframe(self, X, Y, Z, *args, **kwargs):
  1484. """
  1485. Plot a 3D wireframe.
  1486. .. note::
  1487. The *rcount* and *ccount* kwargs, which both default to 50,
  1488. determine the maximum number of samples used in each direction. If
  1489. the input data is larger, it will be downsampled (by slicing) to
  1490. these numbers of points.
  1491. Parameters
  1492. ----------
  1493. X, Y, Z : 2d arrays
  1494. Data values.
  1495. rcount, ccount : int
  1496. Maximum number of samples used in each direction. If the input
  1497. data is larger, it will be downsampled (by slicing) to these
  1498. numbers of points. Setting a count to zero causes the data to be
  1499. not sampled in the corresponding direction, producing a 3D line
  1500. plot rather than a wireframe plot. Defaults to 50.
  1501. .. versionadded:: 2.0
  1502. rstride, cstride : int
  1503. Downsampling stride in each direction. These arguments are
  1504. mutually exclusive with *rcount* and *ccount*. If only one of
  1505. *rstride* or *cstride* is set, the other defaults to 1. Setting a
  1506. stride to zero causes the data to be not sampled in the
  1507. corresponding direction, producing a 3D line plot rather than a
  1508. wireframe plot.
  1509. 'classic' mode uses a default of ``rstride = cstride = 1`` instead
  1510. of the new default of ``rcount = ccount = 50``.
  1511. **kwargs :
  1512. Other arguments are forwarded to `.Line3DCollection`.
  1513. """
  1514. had_data = self.has_data()
  1515. if Z.ndim != 2:
  1516. raise ValueError("Argument Z must be 2-dimensional.")
  1517. # FIXME: Support masked arrays
  1518. X, Y, Z = np.broadcast_arrays(X, Y, Z)
  1519. rows, cols = Z.shape
  1520. has_stride = 'rstride' in kwargs or 'cstride' in kwargs
  1521. has_count = 'rcount' in kwargs or 'ccount' in kwargs
  1522. if has_stride and has_count:
  1523. raise ValueError("Cannot specify both stride and count arguments")
  1524. rstride = kwargs.pop('rstride', 1)
  1525. cstride = kwargs.pop('cstride', 1)
  1526. rcount = kwargs.pop('rcount', 50)
  1527. ccount = kwargs.pop('ccount', 50)
  1528. if rcParams['_internal.classic_mode']:
  1529. # Strides have priority over counts in classic mode.
  1530. # So, only compute strides from counts
  1531. # if counts were explicitly given
  1532. if has_count:
  1533. rstride = int(max(np.ceil(rows / rcount), 1)) if rcount else 0
  1534. cstride = int(max(np.ceil(cols / ccount), 1)) if ccount else 0
  1535. else:
  1536. # If the strides are provided then it has priority.
  1537. # Otherwise, compute the strides from the counts.
  1538. if not has_stride:
  1539. rstride = int(max(np.ceil(rows / rcount), 1)) if rcount else 0
  1540. cstride = int(max(np.ceil(cols / ccount), 1)) if ccount else 0
  1541. # We want two sets of lines, one running along the "rows" of
  1542. # Z and another set of lines running along the "columns" of Z.
  1543. # This transpose will make it easy to obtain the columns.
  1544. tX, tY, tZ = np.transpose(X), np.transpose(Y), np.transpose(Z)
  1545. if rstride:
  1546. rii = list(xrange(0, rows, rstride))
  1547. # Add the last index only if needed
  1548. if rows > 0 and rii[-1] != (rows - 1):
  1549. rii += [rows-1]
  1550. else:
  1551. rii = []
  1552. if cstride:
  1553. cii = list(xrange(0, cols, cstride))
  1554. # Add the last index only if needed
  1555. if cols > 0 and cii[-1] != (cols - 1):
  1556. cii += [cols-1]
  1557. else:
  1558. cii = []
  1559. if rstride == 0 and cstride == 0:
  1560. raise ValueError("Either rstride or cstride must be non zero")
  1561. # If the inputs were empty, then just
  1562. # reset everything.
  1563. if Z.size == 0:
  1564. rii = []
  1565. cii = []
  1566. xlines = [X[i] for i in rii]
  1567. ylines = [Y[i] for i in rii]
  1568. zlines = [Z[i] for i in rii]
  1569. txlines = [tX[i] for i in cii]
  1570. tylines = [tY[i] for i in cii]
  1571. tzlines = [tZ[i] for i in cii]
  1572. lines = ([list(zip(xl, yl, zl))
  1573. for xl, yl, zl in zip(xlines, ylines, zlines)]
  1574. + [list(zip(xl, yl, zl))
  1575. for xl, yl, zl in zip(txlines, tylines, tzlines)])
  1576. linec = art3d.Line3DCollection(lines, *args, **kwargs)
  1577. self.add_collection(linec)
  1578. self.auto_scale_xyz(X, Y, Z, had_data)
  1579. return linec
  1580. def plot_trisurf(self, *args, **kwargs):
  1581. """
  1582. ============= ================================================
  1583. Argument Description
  1584. ============= ================================================
  1585. *X*, *Y*, *Z* Data values as 1D arrays
  1586. *color* Color of the surface patches
  1587. *cmap* A colormap for the surface patches.
  1588. *norm* An instance of Normalize to map values to colors
  1589. *vmin* Minimum value to map
  1590. *vmax* Maximum value to map
  1591. *shade* Whether to shade the facecolors
  1592. ============= ================================================
  1593. The (optional) triangulation can be specified in one of two ways;
  1594. either::
  1595. plot_trisurf(triangulation, ...)
  1596. where triangulation is a :class:`~matplotlib.tri.Triangulation`
  1597. object, or::
  1598. plot_trisurf(X, Y, ...)
  1599. plot_trisurf(X, Y, triangles, ...)
  1600. plot_trisurf(X, Y, triangles=triangles, ...)
  1601. in which case a Triangulation object will be created. See
  1602. :class:`~matplotlib.tri.Triangulation` for a explanation of
  1603. these possibilities.
  1604. The remaining arguments are::
  1605. plot_trisurf(..., Z)
  1606. where *Z* is the array of values to contour, one per point
  1607. in the triangulation.
  1608. Other arguments are passed on to
  1609. :class:`~mpl_toolkits.mplot3d.art3d.Poly3DCollection`
  1610. **Examples:**
  1611. .. plot:: gallery/mplot3d/trisurf3d.py
  1612. .. plot:: gallery/mplot3d/trisurf3d_2.py
  1613. .. versionadded:: 1.2.0
  1614. This plotting function was added for the v1.2.0 release.
  1615. """
  1616. had_data = self.has_data()
  1617. # TODO: Support custom face colours
  1618. color = kwargs.pop('color', None)
  1619. if color is None:
  1620. color = self._get_lines.get_next_color()
  1621. color = np.array(mcolors.to_rgba(color))
  1622. cmap = kwargs.get('cmap', None)
  1623. norm = kwargs.pop('norm', None)
  1624. vmin = kwargs.pop('vmin', None)
  1625. vmax = kwargs.pop('vmax', None)
  1626. linewidth = kwargs.get('linewidth', None)
  1627. shade = kwargs.pop('shade', cmap is None)
  1628. lightsource = kwargs.pop('lightsource', None)
  1629. tri, args, kwargs = Triangulation.get_from_args_and_kwargs(*args, **kwargs)
  1630. if 'Z' in kwargs:
  1631. z = np.asarray(kwargs.pop('Z'))
  1632. else:
  1633. z = np.asarray(args[0])
  1634. # We do this so Z doesn't get passed as an arg to PolyCollection
  1635. args = args[1:]
  1636. triangles = tri.get_masked_triangles()
  1637. xt = tri.x[triangles]
  1638. yt = tri.y[triangles]
  1639. zt = z[triangles]
  1640. # verts = np.stack((xt, yt, zt), axis=-1)
  1641. verts = np.concatenate((
  1642. xt[..., np.newaxis], yt[..., np.newaxis], zt[..., np.newaxis]
  1643. ), axis=-1)
  1644. polyc = art3d.Poly3DCollection(verts, *args, **kwargs)
  1645. if cmap:
  1646. # average over the three points of each triangle
  1647. avg_z = verts[:, :, 2].mean(axis=1)
  1648. polyc.set_array(avg_z)
  1649. if vmin is not None or vmax is not None:
  1650. polyc.set_clim(vmin, vmax)
  1651. if norm is not None:
  1652. polyc.set_norm(norm)
  1653. else:
  1654. if shade:
  1655. v1 = verts[:, 0, :] - verts[:, 1, :]
  1656. v2 = verts[:, 1, :] - verts[:, 2, :]
  1657. normals = np.cross(v1, v2)
  1658. colset = self._shade_colors(color, normals)
  1659. else:
  1660. colset = color
  1661. polyc.set_facecolors(colset)
  1662. self.add_collection(polyc)
  1663. self.auto_scale_xyz(tri.x, tri.y, z, had_data)
  1664. return polyc
  1665. def _3d_extend_contour(self, cset, stride=5):
  1666. '''
  1667. Extend a contour in 3D by creating
  1668. '''
  1669. levels = cset.levels
  1670. colls = cset.collections
  1671. dz = (levels[1] - levels[0]) / 2
  1672. for z, linec in zip(levels, colls):
  1673. topverts = art3d.paths_to_3d_segments(linec.get_paths(), z - dz)
  1674. botverts = art3d.paths_to_3d_segments(linec.get_paths(), z + dz)
  1675. color = linec.get_color()[0]
  1676. polyverts = []
  1677. normals = []
  1678. nsteps = np.round(len(topverts[0]) / stride)
  1679. if nsteps <= 1:
  1680. if len(topverts[0]) > 1:
  1681. nsteps = 2
  1682. else:
  1683. continue
  1684. stepsize = (len(topverts[0]) - 1) / (nsteps - 1)
  1685. for i in range(int(np.round(nsteps)) - 1):
  1686. i1 = int(np.round(i * stepsize))
  1687. i2 = int(np.round((i + 1) * stepsize))
  1688. polyverts.append([topverts[0][i1],
  1689. topverts[0][i2],
  1690. botverts[0][i2],
  1691. botverts[0][i1]])
  1692. v1 = np.array(topverts[0][i1]) - np.array(topverts[0][i2])
  1693. v2 = np.array(topverts[0][i1]) - np.array(botverts[0][i1])
  1694. normals.append(np.cross(v1, v2))
  1695. colors = self._shade_colors(color, normals)
  1696. colors2 = self._shade_colors(color, normals)
  1697. polycol = art3d.Poly3DCollection(polyverts,
  1698. facecolors=colors,
  1699. edgecolors=colors2)
  1700. polycol.set_sort_zpos(z)
  1701. self.add_collection3d(polycol)
  1702. for col in colls:
  1703. self.collections.remove(col)
  1704. def add_contour_set(self, cset, extend3d=False, stride=5, zdir='z', offset=None):
  1705. zdir = '-' + zdir
  1706. if extend3d:
  1707. self._3d_extend_contour(cset, stride)
  1708. else:
  1709. for z, linec in zip(cset.levels, cset.collections):
  1710. if offset is not None:
  1711. z = offset
  1712. art3d.line_collection_2d_to_3d(linec, z, zdir=zdir)
  1713. def add_contourf_set(self, cset, zdir='z', offset=None):
  1714. zdir = '-' + zdir
  1715. for z, linec in zip(cset.levels, cset.collections):
  1716. if offset is not None :
  1717. z = offset
  1718. art3d.poly_collection_2d_to_3d(linec, z, zdir=zdir)
  1719. linec.set_sort_zpos(z)
  1720. def contour(self, X, Y, Z, *args, **kwargs):
  1721. '''
  1722. Create a 3D contour plot.
  1723. ========== ================================================
  1724. Argument Description
  1725. ========== ================================================
  1726. *X*, *Y*, Data values as numpy.arrays
  1727. *Z*
  1728. *extend3d* Whether to extend contour in 3D (default: False)
  1729. *stride* Stride (step size) for extending contour
  1730. *zdir* The direction to use: x, y or z (default)
  1731. *offset* If specified plot a projection of the contour
  1732. lines on this position in plane normal to zdir
  1733. ========== ================================================
  1734. The positional and other keyword arguments are passed on to
  1735. :func:`~matplotlib.axes.Axes.contour`
  1736. Returns a :class:`~matplotlib.axes.Axes.contour`
  1737. '''
  1738. extend3d = kwargs.pop('extend3d', False)
  1739. stride = kwargs.pop('stride', 5)
  1740. zdir = kwargs.pop('zdir', 'z')
  1741. offset = kwargs.pop('offset', None)
  1742. had_data = self.has_data()
  1743. jX, jY, jZ = art3d.rotate_axes(X, Y, Z, zdir)
  1744. cset = super(Axes3D, self).contour(jX, jY, jZ, *args, **kwargs)
  1745. self.add_contour_set(cset, extend3d, stride, zdir, offset)
  1746. self.auto_scale_xyz(X, Y, Z, had_data)
  1747. return cset
  1748. contour3D = contour
  1749. def tricontour(self, *args, **kwargs):
  1750. """
  1751. Create a 3D contour plot.
  1752. ========== ================================================
  1753. Argument Description
  1754. ========== ================================================
  1755. *X*, *Y*, Data values as numpy.arrays
  1756. *Z*
  1757. *extend3d* Whether to extend contour in 3D (default: False)
  1758. *stride* Stride (step size) for extending contour
  1759. *zdir* The direction to use: x, y or z (default)
  1760. *offset* If specified plot a projection of the contour
  1761. lines on this position in plane normal to zdir
  1762. ========== ================================================
  1763. Other keyword arguments are passed on to
  1764. :func:`~matplotlib.axes.Axes.tricontour`
  1765. Returns a :class:`~matplotlib.axes.Axes.contour`
  1766. .. versionchanged:: 1.3.0
  1767. Added support for custom triangulations
  1768. EXPERIMENTAL: This method currently produces incorrect output due to a
  1769. longstanding bug in 3D PolyCollection rendering.
  1770. """
  1771. extend3d = kwargs.pop('extend3d', False)
  1772. stride = kwargs.pop('stride', 5)
  1773. zdir = kwargs.pop('zdir', 'z')
  1774. offset = kwargs.pop('offset', None)
  1775. had_data = self.has_data()
  1776. tri, args, kwargs = Triangulation.get_from_args_and_kwargs(
  1777. *args, **kwargs)
  1778. X = tri.x
  1779. Y = tri.y
  1780. if 'Z' in kwargs:
  1781. Z = kwargs.pop('Z')
  1782. else:
  1783. Z = args[0]
  1784. # We do this so Z doesn't get passed as an arg to Axes.tricontour
  1785. args = args[1:]
  1786. jX, jY, jZ = art3d.rotate_axes(X, Y, Z, zdir)
  1787. tri = Triangulation(jX, jY, tri.triangles, tri.mask)
  1788. cset = super(Axes3D, self).tricontour(tri, jZ, *args, **kwargs)
  1789. self.add_contour_set(cset, extend3d, stride, zdir, offset)
  1790. self.auto_scale_xyz(X, Y, Z, had_data)
  1791. return cset
  1792. def contourf(self, X, Y, Z, *args, **kwargs):
  1793. '''
  1794. Create a 3D contourf plot.
  1795. ========== ================================================
  1796. Argument Description
  1797. ========== ================================================
  1798. *X*, *Y*, Data values as numpy.arrays
  1799. *Z*
  1800. *zdir* The direction to use: x, y or z (default)
  1801. *offset* If specified plot a projection of the filled contour
  1802. on this position in plane normal to zdir
  1803. ========== ================================================
  1804. The positional and keyword arguments are passed on to
  1805. :func:`~matplotlib.axes.Axes.contourf`
  1806. Returns a :class:`~matplotlib.axes.Axes.contourf`
  1807. .. versionchanged :: 1.1.0
  1808. The *zdir* and *offset* kwargs were added.
  1809. '''
  1810. zdir = kwargs.pop('zdir', 'z')
  1811. offset = kwargs.pop('offset', None)
  1812. had_data = self.has_data()
  1813. jX, jY, jZ = art3d.rotate_axes(X, Y, Z, zdir)
  1814. cset = super(Axes3D, self).contourf(jX, jY, jZ, *args, **kwargs)
  1815. self.add_contourf_set(cset, zdir, offset)
  1816. self.auto_scale_xyz(X, Y, Z, had_data)
  1817. return cset
  1818. contourf3D = contourf
  1819. def tricontourf(self, *args, **kwargs):
  1820. """
  1821. Create a 3D contourf plot.
  1822. ========== ================================================
  1823. Argument Description
  1824. ========== ================================================
  1825. *X*, *Y*, Data values as numpy.arrays
  1826. *Z*
  1827. *zdir* The direction to use: x, y or z (default)
  1828. *offset* If specified plot a projection of the contour
  1829. lines on this position in plane normal to zdir
  1830. ========== ================================================
  1831. Other keyword arguments are passed on to
  1832. :func:`~matplotlib.axes.Axes.tricontour`
  1833. Returns a :class:`~matplotlib.axes.Axes.contour`
  1834. .. versionchanged :: 1.3.0
  1835. Added support for custom triangulations
  1836. EXPERIMENTAL: This method currently produces incorrect output due to a
  1837. longstanding bug in 3D PolyCollection rendering.
  1838. """
  1839. zdir = kwargs.pop('zdir', 'z')
  1840. offset = kwargs.pop('offset', None)
  1841. had_data = self.has_data()
  1842. tri, args, kwargs = Triangulation.get_from_args_and_kwargs(
  1843. *args, **kwargs)
  1844. X = tri.x
  1845. Y = tri.y
  1846. if 'Z' in kwargs:
  1847. Z = kwargs.pop('Z')
  1848. else:
  1849. Z = args[0]
  1850. # We do this so Z doesn't get passed as an arg to Axes.tricontourf
  1851. args = args[1:]
  1852. jX, jY, jZ = art3d.rotate_axes(X, Y, Z, zdir)
  1853. tri = Triangulation(jX, jY, tri.triangles, tri.mask)
  1854. cset = super(Axes3D, self).tricontourf(tri, jZ, *args, **kwargs)
  1855. self.add_contourf_set(cset, zdir, offset)
  1856. self.auto_scale_xyz(X, Y, Z, had_data)
  1857. return cset
  1858. def add_collection3d(self, col, zs=0, zdir='z'):
  1859. '''
  1860. Add a 3D collection object to the plot.
  1861. 2D collection types are converted to a 3D version by
  1862. modifying the object and adding z coordinate information.
  1863. Supported are:
  1864. - PolyCollection
  1865. - LineCollection
  1866. - PatchCollection
  1867. '''
  1868. zvals = np.atleast_1d(zs)
  1869. if len(zvals) > 0 :
  1870. zsortval = min(zvals)
  1871. else :
  1872. zsortval = 0 # FIXME: Fairly arbitrary. Is there a better value?
  1873. # FIXME: use issubclass() (although, then a 3D collection
  1874. # object would also pass.) Maybe have a collection3d
  1875. # abstract class to test for and exclude?
  1876. if type(col) is mcoll.PolyCollection:
  1877. art3d.poly_collection_2d_to_3d(col, zs=zs, zdir=zdir)
  1878. col.set_sort_zpos(zsortval)
  1879. elif type(col) is mcoll.LineCollection:
  1880. art3d.line_collection_2d_to_3d(col, zs=zs, zdir=zdir)
  1881. col.set_sort_zpos(zsortval)
  1882. elif type(col) is mcoll.PatchCollection:
  1883. art3d.patch_collection_2d_to_3d(col, zs=zs, zdir=zdir)
  1884. col.set_sort_zpos(zsortval)
  1885. super(Axes3D, self).add_collection(col)
  1886. def scatter(self, xs, ys, zs=0, zdir='z', s=20, c=None, depthshade=True,
  1887. *args, **kwargs):
  1888. '''
  1889. Create a scatter plot.
  1890. ============ ========================================================
  1891. Argument Description
  1892. ============ ========================================================
  1893. *xs*, *ys* Positions of data points.
  1894. *zs* Either an array of the same length as *xs* and
  1895. *ys* or a single value to place all points in
  1896. the same plane. Default is 0.
  1897. *zdir* Which direction to use as z ('x', 'y' or 'z')
  1898. when plotting a 2D set.
  1899. *s* Size in points^2. It is a scalar or an array of the
  1900. same length as *x* and *y*.
  1901. *c* A color. *c* can be a single color format string, or a
  1902. sequence of color specifications of length *N*, or a
  1903. sequence of *N* numbers to be mapped to colors using the
  1904. *cmap* and *norm* specified via kwargs (see below). Note
  1905. that *c* should not be a single numeric RGB or RGBA
  1906. sequence because that is indistinguishable from an array
  1907. of values to be colormapped. *c* can be a 2-D array in
  1908. which the rows are RGB or RGBA, however, including the
  1909. case of a single row to specify the same color for
  1910. all points.
  1911. *depthshade*
  1912. Whether or not to shade the scatter markers to give
  1913. the appearance of depth. Default is *True*.
  1914. ============ ========================================================
  1915. Keyword arguments are passed on to
  1916. :func:`~matplotlib.axes.Axes.scatter`.
  1917. Returns a :class:`~mpl_toolkits.mplot3d.art3d.Patch3DCollection`
  1918. '''
  1919. had_data = self.has_data()
  1920. xs, ys, zs = np.broadcast_arrays(
  1921. *[np.ravel(np.ma.filled(t, np.nan)) for t in [xs, ys, zs]])
  1922. s = np.ma.ravel(s) # This doesn't have to match x, y in size.
  1923. xs, ys, zs, s, c = cbook.delete_masked_points(xs, ys, zs, s, c)
  1924. patches = super(Axes3D, self).scatter(
  1925. xs, ys, s=s, c=c, *args, **kwargs)
  1926. is_2d = not cbook.iterable(zs)
  1927. zs = _backports.broadcast_to(zs, len(xs))
  1928. art3d.patch_collection_2d_to_3d(patches, zs=zs, zdir=zdir,
  1929. depthshade=depthshade)
  1930. if self._zmargin < 0.05 and xs.size > 0:
  1931. self.set_zmargin(0.05)
  1932. #FIXME: why is this necessary?
  1933. if not is_2d:
  1934. self.auto_scale_xyz(xs, ys, zs, had_data)
  1935. return patches
  1936. scatter3D = scatter
  1937. def bar(self, left, height, zs=0, zdir='z', *args, **kwargs):
  1938. '''
  1939. Add 2D bar(s).
  1940. ========== ================================================
  1941. Argument Description
  1942. ========== ================================================
  1943. *left* The x coordinates of the left sides of the bars.
  1944. *height* The height of the bars.
  1945. *zs* Z coordinate of bars, if one value is specified
  1946. they will all be placed at the same z.
  1947. *zdir* Which direction to use as z ('x', 'y' or 'z')
  1948. when plotting a 2D set.
  1949. ========== ================================================
  1950. Keyword arguments are passed onto :func:`~matplotlib.axes.Axes.bar`.
  1951. Returns a :class:`~mpl_toolkits.mplot3d.art3d.Patch3DCollection`
  1952. '''
  1953. had_data = self.has_data()
  1954. patches = super(Axes3D, self).bar(left, height, *args, **kwargs)
  1955. zs = _backports.broadcast_to(zs, len(left))
  1956. verts = []
  1957. verts_zs = []
  1958. for p, z in zip(patches, zs):
  1959. vs = art3d.get_patch_verts(p)
  1960. verts += vs.tolist()
  1961. verts_zs += [z] * len(vs)
  1962. art3d.patch_2d_to_3d(p, z, zdir)
  1963. if 'alpha' in kwargs:
  1964. p.set_alpha(kwargs['alpha'])
  1965. if len(verts) > 0 :
  1966. # the following has to be skipped if verts is empty
  1967. # NOTE: Bugs could still occur if len(verts) > 0,
  1968. # but the "2nd dimension" is empty.
  1969. xs, ys = list(zip(*verts))
  1970. else :
  1971. xs, ys = [], []
  1972. xs, ys, verts_zs = art3d.juggle_axes(xs, ys, verts_zs, zdir)
  1973. self.auto_scale_xyz(xs, ys, verts_zs, had_data)
  1974. return patches
  1975. def bar3d(self, x, y, z, dx, dy, dz, color=None,
  1976. zsort='average', shade=True, *args, **kwargs):
  1977. """Generate a 3D barplot.
  1978. This method creates three dimensional barplot where the width,
  1979. depth, height, and color of the bars can all be uniquely set.
  1980. Parameters
  1981. ----------
  1982. x, y, z : array-like
  1983. The coordinates of the anchor point of the bars.
  1984. dx, dy, dz : scalar or array-like
  1985. The width, depth, and height of the bars, respectively.
  1986. color : sequence of valid color specifications, optional
  1987. The color of the bars can be specified globally or
  1988. individually. This parameter can be:
  1989. - A single color value, to color all bars the same color.
  1990. - An array of colors of length N bars, to color each bar
  1991. independently.
  1992. - An array of colors of length 6, to color the faces of the
  1993. bars similarly.
  1994. - An array of colors of length 6 * N bars, to color each face
  1995. independently.
  1996. When coloring the faces of the boxes specifically, this is
  1997. the order of the coloring:
  1998. 1. -Z (bottom of box)
  1999. 2. +Z (top of box)
  2000. 3. -Y
  2001. 4. +Y
  2002. 5. -X
  2003. 6. +X
  2004. zsort : str, optional
  2005. The z-axis sorting scheme passed onto
  2006. :func:`~mpl_toolkits.mplot3d.art3d.Poly3DCollection`
  2007. shade : bool, optional (default = True)
  2008. When true, this shades the dark sides of the bars (relative
  2009. to the plot's source of light).
  2010. Any additional keyword arguments are passed onto
  2011. :func:`~mpl_toolkits.mplot3d.art3d.Poly3DCollection`
  2012. Returns
  2013. -------
  2014. collection : Poly3DCollection
  2015. A collection of three dimensional polygons representing
  2016. the bars.
  2017. """
  2018. had_data = self.has_data()
  2019. x, y, z, dx, dy, dz = np.broadcast_arrays(
  2020. np.atleast_1d(x), y, z, dx, dy, dz)
  2021. minx = np.min(x)
  2022. maxx = np.max(x + dx)
  2023. miny = np.min(y)
  2024. maxy = np.max(y + dy)
  2025. minz = np.min(z)
  2026. maxz = np.max(z + dz)
  2027. polys = []
  2028. for xi, yi, zi, dxi, dyi, dzi in zip(x, y, z, dx, dy, dz):
  2029. polys.extend([
  2030. ((xi, yi, zi), (xi + dxi, yi, zi),
  2031. (xi + dxi, yi + dyi, zi), (xi, yi + dyi, zi)),
  2032. ((xi, yi, zi + dzi), (xi + dxi, yi, zi + dzi),
  2033. (xi + dxi, yi + dyi, zi + dzi), (xi, yi + dyi, zi + dzi)),
  2034. ((xi, yi, zi), (xi + dxi, yi, zi),
  2035. (xi + dxi, yi, zi + dzi), (xi, yi, zi + dzi)),
  2036. ((xi, yi + dyi, zi), (xi + dxi, yi + dyi, zi),
  2037. (xi + dxi, yi + dyi, zi + dzi), (xi, yi + dyi, zi + dzi)),
  2038. ((xi, yi, zi), (xi, yi + dyi, zi),
  2039. (xi, yi + dyi, zi + dzi), (xi, yi, zi + dzi)),
  2040. ((xi + dxi, yi, zi), (xi + dxi, yi + dyi, zi),
  2041. (xi + dxi, yi + dyi, zi + dzi), (xi + dxi, yi, zi + dzi)),
  2042. ])
  2043. facecolors = []
  2044. if color is None:
  2045. color = [self._get_patches_for_fill.get_next_color()]
  2046. if len(color) == len(x):
  2047. # bar colors specified, need to expand to number of faces
  2048. for c in color:
  2049. facecolors.extend([c] * 6)
  2050. else:
  2051. # a single color specified, or face colors specified explicitly
  2052. facecolors = list(mcolors.to_rgba_array(color))
  2053. if len(facecolors) < len(x):
  2054. facecolors *= (6 * len(x))
  2055. if shade:
  2056. normals = self._generate_normals(polys)
  2057. sfacecolors = self._shade_colors(facecolors, normals)
  2058. else:
  2059. sfacecolors = facecolors
  2060. col = art3d.Poly3DCollection(polys,
  2061. zsort=zsort,
  2062. facecolor=sfacecolors,
  2063. *args, **kwargs)
  2064. self.add_collection(col)
  2065. self.auto_scale_xyz((minx, maxx), (miny, maxy), (minz, maxz), had_data)
  2066. return col
  2067. def set_title(self, label, fontdict=None, loc='center', **kwargs):
  2068. ret = super(Axes3D, self).set_title(label, fontdict=fontdict, loc=loc,
  2069. **kwargs)
  2070. (x, y) = self.title.get_position()
  2071. self.title.set_y(0.92 * y)
  2072. return ret
  2073. set_title.__doc__ = maxes.Axes.set_title.__doc__
  2074. def quiver(self, *args, **kwargs):
  2075. """
  2076. Plot a 3D field of arrows.
  2077. call signatures::
  2078. quiver(X, Y, Z, U, V, W, **kwargs)
  2079. Arguments:
  2080. *X*, *Y*, *Z*:
  2081. The x, y and z coordinates of the arrow locations (default is
  2082. tail of arrow; see *pivot* kwarg)
  2083. *U*, *V*, *W*:
  2084. The x, y and z components of the arrow vectors
  2085. The arguments could be array-like or scalars, so long as they
  2086. they can be broadcast together. The arguments can also be
  2087. masked arrays. If an element in any of argument is masked, then
  2088. that corresponding quiver element will not be plotted.
  2089. Keyword arguments:
  2090. *length*: [1.0 | float]
  2091. The length of each quiver, default to 1.0, the unit is
  2092. the same with the axes
  2093. *arrow_length_ratio*: [0.3 | float]
  2094. The ratio of the arrow head with respect to the quiver,
  2095. default to 0.3
  2096. *pivot*: [ 'tail' | 'middle' | 'tip' ]
  2097. The part of the arrow that is at the grid point; the arrow
  2098. rotates about this point, hence the name *pivot*.
  2099. Default is 'tail'
  2100. *normalize*: bool
  2101. When True, all of the arrows will be the same length. This
  2102. defaults to False, where the arrows will be different lengths
  2103. depending on the values of u,v,w.
  2104. Any additional keyword arguments are delegated to
  2105. :class:`~matplotlib.collections.LineCollection`
  2106. """
  2107. def calc_arrow(uvw, angle=15):
  2108. """
  2109. To calculate the arrow head. uvw should be a unit vector.
  2110. We normalize it here:
  2111. """
  2112. # get unit direction vector perpendicular to (u,v,w)
  2113. norm = np.linalg.norm(uvw[:2])
  2114. if norm > 0:
  2115. x = uvw[1] / norm
  2116. y = -uvw[0] / norm
  2117. else:
  2118. x, y = 0, 1
  2119. # compute the two arrowhead direction unit vectors
  2120. ra = math.radians(angle)
  2121. c = math.cos(ra)
  2122. s = math.sin(ra)
  2123. # construct the rotation matrices
  2124. Rpos = np.array([[c+(x**2)*(1-c), x*y*(1-c), y*s],
  2125. [y*x*(1-c), c+(y**2)*(1-c), -x*s],
  2126. [-y*s, x*s, c]])
  2127. # opposite rotation negates all the sin terms
  2128. Rneg = Rpos.copy()
  2129. Rneg[[0,1,2,2],[2,2,0,1]] = -Rneg[[0,1,2,2],[2,2,0,1]]
  2130. # multiply them to get the rotated vector
  2131. return Rpos.dot(uvw), Rneg.dot(uvw)
  2132. had_data = self.has_data()
  2133. # handle kwargs
  2134. # shaft length
  2135. length = kwargs.pop('length', 1)
  2136. # arrow length ratio to the shaft length
  2137. arrow_length_ratio = kwargs.pop('arrow_length_ratio', 0.3)
  2138. # pivot point
  2139. pivot = kwargs.pop('pivot', 'tail')
  2140. # normalize
  2141. normalize = kwargs.pop('normalize', False)
  2142. # handle args
  2143. argi = 6
  2144. if len(args) < argi:
  2145. raise ValueError('Wrong number of arguments. Expected %d got %d' %
  2146. (argi, len(args)))
  2147. # first 6 arguments are X, Y, Z, U, V, W
  2148. input_args = args[:argi]
  2149. # if any of the args are scalar, convert into list
  2150. input_args = [[k] if isinstance(k, (int, float)) else k
  2151. for k in input_args]
  2152. # extract the masks, if any
  2153. masks = [k.mask for k in input_args if isinstance(k, np.ma.MaskedArray)]
  2154. # broadcast to match the shape
  2155. bcast = np.broadcast_arrays(*(input_args + masks))
  2156. input_args = bcast[:argi]
  2157. masks = bcast[argi:]
  2158. if masks:
  2159. # combine the masks into one
  2160. mask = reduce(np.logical_or, masks)
  2161. # put mask on and compress
  2162. input_args = [np.ma.array(k, mask=mask).compressed()
  2163. for k in input_args]
  2164. else:
  2165. input_args = [k.flatten() for k in input_args]
  2166. if any(len(v) == 0 for v in input_args):
  2167. # No quivers, so just make an empty collection and return early
  2168. linec = art3d.Line3DCollection([], *args[argi:], **kwargs)
  2169. self.add_collection(linec)
  2170. return linec
  2171. # Following assertions must be true before proceeding
  2172. # must all be ndarray
  2173. assert all(isinstance(k, np.ndarray) for k in input_args)
  2174. # must all in same shape
  2175. assert len({k.shape for k in input_args}) == 1
  2176. shaft_dt = np.linspace(0, length, num=2)
  2177. arrow_dt = shaft_dt * arrow_length_ratio
  2178. if pivot == 'tail':
  2179. shaft_dt -= length
  2180. elif pivot == 'middle':
  2181. shaft_dt -= length/2.
  2182. elif pivot != 'tip':
  2183. raise ValueError('Invalid pivot argument: ' + str(pivot))
  2184. XYZ = np.column_stack(input_args[:3])
  2185. UVW = np.column_stack(input_args[3:argi]).astype(float)
  2186. # Normalize rows of UVW
  2187. # Note: with numpy 1.9+, could use np.linalg.norm(UVW, axis=1)
  2188. norm = np.sqrt(np.sum(UVW**2, axis=1))
  2189. # If any row of UVW is all zeros, don't make a quiver for it
  2190. mask = norm > 0
  2191. XYZ = XYZ[mask]
  2192. if normalize:
  2193. UVW = UVW[mask] / norm[mask].reshape((-1, 1))
  2194. else:
  2195. UVW = UVW[mask]
  2196. if len(XYZ) > 0:
  2197. # compute the shaft lines all at once with an outer product
  2198. shafts = (XYZ - np.multiply.outer(shaft_dt, UVW)).swapaxes(0, 1)
  2199. # compute head direction vectors, n heads by 2 sides by 3 dimensions
  2200. head_dirs = np.array([calc_arrow(d) for d in UVW])
  2201. # compute all head lines at once, starting from where the shaft ends
  2202. heads = shafts[:, :1] - np.multiply.outer(arrow_dt, head_dirs)
  2203. # stack left and right head lines together
  2204. heads.shape = (len(arrow_dt), -1, 3)
  2205. # transpose to get a list of lines
  2206. heads = heads.swapaxes(0, 1)
  2207. lines = list(shafts) + list(heads)
  2208. else:
  2209. lines = []
  2210. linec = art3d.Line3DCollection(lines, *args[argi:], **kwargs)
  2211. self.add_collection(linec)
  2212. self.auto_scale_xyz(XYZ[:, 0], XYZ[:, 1], XYZ[:, 2], had_data)
  2213. return linec
  2214. quiver3D = quiver
  2215. def voxels(self, *args, **kwargs):
  2216. """
  2217. ax.voxels([x, y, z,] /, filled, **kwargs)
  2218. Plot a set of filled voxels
  2219. All voxels are plotted as 1x1x1 cubes on the axis, with filled[0,0,0]
  2220. placed with its lower corner at the origin. Occluded faces are not
  2221. plotted.
  2222. Call signatures::
  2223. voxels(filled, facecolors=fc, edgecolors=ec, **kwargs)
  2224. voxels(x, y, z, filled, facecolors=fc, edgecolors=ec, **kwargs)
  2225. .. versionadded:: 2.1
  2226. Parameters
  2227. ----------
  2228. filled : 3D np.array of bool
  2229. A 3d array of values, with truthy values indicating which voxels
  2230. to fill
  2231. x, y, z : 3D np.array, optional
  2232. The coordinates of the corners of the voxels. This should broadcast
  2233. to a shape one larger in every dimension than the shape of `filled`.
  2234. These can be used to plot non-cubic voxels.
  2235. If not specified, defaults to increasing integers along each axis,
  2236. like those returned by :func:`~numpy.indices`.
  2237. As indicated by the ``/`` in the function signature, these arguments
  2238. can only be passed positionally.
  2239. facecolors, edgecolors : array_like, optional
  2240. The color to draw the faces and edges of the voxels. Can only be
  2241. passed as keyword arguments.
  2242. This parameter can be:
  2243. - A single color value, to color all voxels the same color. This
  2244. can be either a string, or a 1D rgb/rgba array
  2245. - ``None``, the default, to use a single color for the faces, and
  2246. the style default for the edges.
  2247. - A 3D ndarray of color names, with each item the color for the
  2248. corresponding voxel. The size must match the voxels.
  2249. - A 4D ndarray of rgb/rgba data, with the components along the
  2250. last axis.
  2251. **kwargs
  2252. Additional keyword arguments to pass onto
  2253. :func:`~mpl_toolkits.mplot3d.art3d.Poly3DCollection`
  2254. Returns
  2255. -------
  2256. faces : dict
  2257. A dictionary indexed by coordinate, where ``faces[i,j,k]`` is a
  2258. `Poly3DCollection` of the faces drawn for the voxel
  2259. ``filled[i,j,k]``. If no faces were drawn for a given voxel, either
  2260. because it was not asked to be drawn, or it is fully occluded, then
  2261. ``(i,j,k) not in faces``.
  2262. Examples
  2263. --------
  2264. .. plot:: gallery/mplot3d/voxels.py
  2265. .. plot:: gallery/mplot3d/voxels_rgb.py
  2266. .. plot:: gallery/mplot3d/voxels_torus.py
  2267. .. plot:: gallery/mplot3d/voxels_numpy_logo.py
  2268. """
  2269. # work out which signature we should be using, and use it to parse
  2270. # the arguments. Name must be voxels for the correct error message
  2271. if len(args) >= 3:
  2272. # underscores indicate position only
  2273. def voxels(__x, __y, __z, filled, **kwargs):
  2274. return (__x, __y, __z), filled, kwargs
  2275. else:
  2276. def voxels(filled, **kwargs):
  2277. return None, filled, kwargs
  2278. xyz, filled, kwargs = voxels(*args, **kwargs)
  2279. # check dimensions
  2280. if filled.ndim != 3:
  2281. raise ValueError("Argument filled must be 3-dimensional")
  2282. size = np.array(filled.shape, dtype=np.intp)
  2283. # check xyz coordinates, which are one larger than the filled shape
  2284. coord_shape = tuple(size + 1)
  2285. if xyz is None:
  2286. x, y, z = np.indices(coord_shape)
  2287. else:
  2288. x, y, z = (_backports.broadcast_to(c, coord_shape) for c in xyz)
  2289. def _broadcast_color_arg(color, name):
  2290. if np.ndim(color) in (0, 1):
  2291. # single color, like "red" or [1, 0, 0]
  2292. return _backports.broadcast_to(
  2293. color, filled.shape + np.shape(color))
  2294. elif np.ndim(color) in (3, 4):
  2295. # 3D array of strings, or 4D array with last axis rgb
  2296. if np.shape(color)[:3] != filled.shape:
  2297. raise ValueError(
  2298. "When multidimensional, {} must match the shape of "
  2299. "filled".format(name))
  2300. return color
  2301. else:
  2302. raise ValueError("Invalid {} argument".format(name))
  2303. # intercept the facecolors, handling defaults and broacasting
  2304. facecolors = kwargs.pop('facecolors', None)
  2305. if facecolors is None:
  2306. facecolors = self._get_patches_for_fill.get_next_color()
  2307. facecolors = _broadcast_color_arg(facecolors, 'facecolors')
  2308. # broadcast but no default on edgecolors
  2309. edgecolors = kwargs.pop('edgecolors', None)
  2310. edgecolors = _broadcast_color_arg(edgecolors, 'edgecolors')
  2311. # always scale to the full array, even if the data is only in the center
  2312. self.auto_scale_xyz(x, y, z)
  2313. # points lying on corners of a square
  2314. square = np.array([
  2315. [0, 0, 0],
  2316. [0, 1, 0],
  2317. [1, 1, 0],
  2318. [1, 0, 0]
  2319. ], dtype=np.intp)
  2320. voxel_faces = defaultdict(list)
  2321. def permutation_matrices(n):
  2322. """ Generator of cyclic permutation matices """
  2323. mat = np.eye(n, dtype=np.intp)
  2324. for i in range(n):
  2325. yield mat
  2326. mat = np.roll(mat, 1, axis=0)
  2327. # iterate over each of the YZ, ZX, and XY orientations, finding faces to
  2328. # render
  2329. for permute in permutation_matrices(3):
  2330. # find the set of ranges to iterate over
  2331. pc, qc, rc = permute.T.dot(size)
  2332. pinds = np.arange(pc)
  2333. qinds = np.arange(qc)
  2334. rinds = np.arange(rc)
  2335. square_rot = square.dot(permute.T)
  2336. # iterate within the current plane
  2337. for p in pinds:
  2338. for q in qinds:
  2339. # iterate perpendicularly to the current plane, handling
  2340. # boundaries. We only draw faces between a voxel and an
  2341. # empty space, to avoid drawing internal faces.
  2342. # draw lower faces
  2343. p0 = permute.dot([p, q, 0])
  2344. i0 = tuple(p0)
  2345. if filled[i0]:
  2346. voxel_faces[i0].append(p0 + square_rot)
  2347. # draw middle faces
  2348. for r1, r2 in zip(rinds[:-1], rinds[1:]):
  2349. p1 = permute.dot([p, q, r1])
  2350. p2 = permute.dot([p, q, r2])
  2351. i1 = tuple(p1)
  2352. i2 = tuple(p2)
  2353. if filled[i1] and not filled[i2]:
  2354. voxel_faces[i1].append(p2 + square_rot)
  2355. elif not filled[i1] and filled[i2]:
  2356. voxel_faces[i2].append(p2 + square_rot)
  2357. # draw upper faces
  2358. pk = permute.dot([p, q, rc-1])
  2359. pk2 = permute.dot([p, q, rc])
  2360. ik = tuple(pk)
  2361. if filled[ik]:
  2362. voxel_faces[ik].append(pk2 + square_rot)
  2363. # iterate over the faces, and generate a Poly3DCollection for each voxel
  2364. polygons = {}
  2365. for coord, faces_inds in voxel_faces.items():
  2366. # convert indices into 3D positions
  2367. if xyz is None:
  2368. faces = faces_inds
  2369. else:
  2370. faces = []
  2371. for face_inds in faces_inds:
  2372. ind = face_inds[:, 0], face_inds[:, 1], face_inds[:, 2]
  2373. face = np.empty(face_inds.shape)
  2374. face[:, 0] = x[ind]
  2375. face[:, 1] = y[ind]
  2376. face[:, 2] = z[ind]
  2377. faces.append(face)
  2378. poly = art3d.Poly3DCollection(faces,
  2379. facecolors=facecolors[coord],
  2380. edgecolors=edgecolors[coord],
  2381. **kwargs
  2382. )
  2383. self.add_collection3d(poly)
  2384. polygons[coord] = poly
  2385. return polygons
  2386. def get_test_data(delta=0.05):
  2387. '''
  2388. Return a tuple X, Y, Z with a test data set.
  2389. '''
  2390. x = y = np.arange(-3.0, 3.0, delta)
  2391. X, Y = np.meshgrid(x, y)
  2392. Z1 = np.exp(-(X**2 + Y**2) / 2) / (2 * np.pi)
  2393. Z2 = (np.exp(-(((X - 1) / 1.5)**2 + ((Y - 1) / 0.5)**2) / 2) /
  2394. (2 * np.pi * 0.5 * 1.5))
  2395. Z = Z2 - Z1
  2396. X = X * 10
  2397. Y = Y * 10
  2398. Z = Z * 500
  2399. return X, Y, Z
  2400. ########################################################
  2401. # Register Axes3D as a 'projection' object available
  2402. # for use just like any other axes
  2403. ########################################################
  2404. import matplotlib.projections as proj
  2405. proj.projection_registry.register(Axes3D)