axis.py 88 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496
  1. """
  2. Classes for the ticks and x and y axis.
  3. """
  4. import datetime
  5. import logging
  6. import numpy as np
  7. from matplotlib import rcParams
  8. import matplotlib.artist as martist
  9. import matplotlib.cbook as cbook
  10. import matplotlib.font_manager as font_manager
  11. import matplotlib.lines as mlines
  12. import matplotlib.scale as mscale
  13. import matplotlib.text as mtext
  14. import matplotlib.ticker as mticker
  15. import matplotlib.transforms as mtransforms
  16. import matplotlib.units as munits
  17. _log = logging.getLogger(__name__)
  18. GRIDLINE_INTERPOLATION_STEPS = 180
  19. # This list is being used for compatibility with Axes.grid, which
  20. # allows all Line2D kwargs.
  21. _line_AI = martist.ArtistInspector(mlines.Line2D)
  22. _line_param_names = _line_AI.get_setters()
  23. _line_param_aliases = [list(d)[0] for d in _line_AI.aliasd.values()]
  24. _gridline_param_names = ['grid_' + name
  25. for name in _line_param_names + _line_param_aliases]
  26. class Tick(martist.Artist):
  27. """
  28. Abstract base class for the axis ticks, grid lines and labels.
  29. Ticks mark a position on an Axis. They contain two lines as markers and
  30. two labels; one each for the bottom and top positions (in case of an
  31. `.XAxis`) or for the left and right positions (in case of a `.YAxis`).
  32. Attributes
  33. ----------
  34. tick1line : `.Line2D`
  35. The left/bottom tick marker.
  36. tick2line : `.Line2D`
  37. The right/top tick marker.
  38. gridline : `.Line2D`
  39. The grid line associated with the label position.
  40. label1 : `.Text`
  41. The left/bottom tick label.
  42. label2 : `.Text`
  43. The right/top tick label.
  44. """
  45. def __init__(self, axes, loc, label,
  46. size=None, # points
  47. width=None,
  48. color=None,
  49. tickdir=None,
  50. pad=None,
  51. labelsize=None,
  52. labelcolor=None,
  53. zorder=None,
  54. gridOn=None, # defaults to axes.grid depending on
  55. # axes.grid.which
  56. tick1On=True,
  57. tick2On=True,
  58. label1On=True,
  59. label2On=False,
  60. major=True,
  61. labelrotation=0,
  62. grid_color=None,
  63. grid_linestyle=None,
  64. grid_linewidth=None,
  65. grid_alpha=None,
  66. **kw # Other Line2D kwargs applied to gridlines.
  67. ):
  68. """
  69. bbox is the Bound2D bounding box in display coords of the Axes
  70. loc is the tick location in data coords
  71. size is the tick size in points
  72. """
  73. martist.Artist.__init__(self)
  74. if gridOn is None:
  75. if major and (rcParams['axes.grid.which'] in ('both', 'major')):
  76. gridOn = rcParams['axes.grid']
  77. elif (not major) and (rcParams['axes.grid.which']
  78. in ('both', 'minor')):
  79. gridOn = rcParams['axes.grid']
  80. else:
  81. gridOn = False
  82. self.set_figure(axes.figure)
  83. self.axes = axes
  84. name = self.__name__.lower()
  85. self._loc = loc
  86. if size is None:
  87. if major:
  88. size = rcParams['%s.major.size' % name]
  89. else:
  90. size = rcParams['%s.minor.size' % name]
  91. self._size = size
  92. if width is None:
  93. if major:
  94. width = rcParams['%s.major.width' % name]
  95. else:
  96. width = rcParams['%s.minor.width' % name]
  97. self._width = width
  98. if color is None:
  99. color = rcParams['%s.color' % name]
  100. self._color = color
  101. if pad is None:
  102. if major:
  103. pad = rcParams['%s.major.pad' % name]
  104. else:
  105. pad = rcParams['%s.minor.pad' % name]
  106. self._base_pad = pad
  107. if labelcolor is None:
  108. labelcolor = rcParams['%s.color' % name]
  109. self._labelcolor = labelcolor
  110. if labelsize is None:
  111. labelsize = rcParams['%s.labelsize' % name]
  112. self._labelsize = labelsize
  113. self._set_labelrotation(labelrotation)
  114. if zorder is None:
  115. if major:
  116. zorder = mlines.Line2D.zorder + 0.01
  117. else:
  118. zorder = mlines.Line2D.zorder
  119. self._zorder = zorder
  120. self._grid_color = (rcParams['grid.color']
  121. if grid_color is None else grid_color)
  122. self._grid_linestyle = (rcParams['grid.linestyle']
  123. if grid_linestyle is None else grid_linestyle)
  124. self._grid_linewidth = (rcParams['grid.linewidth']
  125. if grid_linewidth is None else grid_linewidth)
  126. self._grid_alpha = (rcParams['grid.alpha']
  127. if grid_alpha is None else grid_alpha)
  128. self._grid_kw = {k[5:]: v for k, v in kw.items()}
  129. self.apply_tickdir(tickdir)
  130. self.tick1line = self._get_tick1line()
  131. self.tick2line = self._get_tick2line()
  132. self.gridline = self._get_gridline()
  133. self.label1 = self._get_text1()
  134. self.label2 = self._get_text2()
  135. self.gridline.set_visible(gridOn)
  136. self.tick1line.set_visible(tick1On)
  137. self.tick2line.set_visible(tick2On)
  138. self.label1.set_visible(label1On)
  139. self.label2.set_visible(label2On)
  140. self.update_position(loc)
  141. for _old_name, _new_name in [
  142. ("gridOn", "gridline"),
  143. ("tick1On", "tick1line"),
  144. ("tick2On", "tick2line"),
  145. ("label1On", "label1"),
  146. ("label2On", "label2")]:
  147. locals()[_old_name] = property(
  148. cbook.deprecated(
  149. "3.1",
  150. name=_old_name,
  151. alternative="Tick.{}.get_visible".format(_new_name))(
  152. lambda self, _new_name=_new_name:
  153. getattr(self, _new_name).get_visible()),
  154. cbook.deprecated(
  155. "3.1",
  156. name=_old_name,
  157. alternative="Tick.{}.set_visible".format(_new_name))(
  158. lambda self, value, _new_name=_new_name:
  159. getattr(self, _new_name).set_visible(value)))
  160. del _old_name, _new_name
  161. @property
  162. @cbook.deprecated("3.1", alternative="Tick.label1", pending=True)
  163. def label(self):
  164. return self.label1
  165. def _set_labelrotation(self, labelrotation):
  166. if isinstance(labelrotation, str):
  167. mode = labelrotation
  168. angle = 0
  169. elif isinstance(labelrotation, (tuple, list)):
  170. mode, angle = labelrotation
  171. else:
  172. mode = 'default'
  173. angle = labelrotation
  174. cbook._check_in_list(['auto', 'default'], labelrotation=mode)
  175. self._labelrotation = (mode, angle)
  176. def apply_tickdir(self, tickdir):
  177. """Calculate self._pad and self._tickmarkers."""
  178. def get_tickdir(self):
  179. return self._tickdir
  180. def get_tick_padding(self):
  181. """Get the length of the tick outside of the axes."""
  182. padding = {
  183. 'in': 0.0,
  184. 'inout': 0.5,
  185. 'out': 1.0
  186. }
  187. return self._size * padding[self._tickdir]
  188. def get_children(self):
  189. children = [self.tick1line, self.tick2line,
  190. self.gridline, self.label1, self.label2]
  191. return children
  192. def set_clip_path(self, clippath, transform=None):
  193. # docstring inherited
  194. martist.Artist.set_clip_path(self, clippath, transform)
  195. self.gridline.set_clip_path(clippath, transform)
  196. self.stale = True
  197. def get_pad_pixels(self):
  198. return self.figure.dpi * self._base_pad / 72
  199. def contains(self, mouseevent):
  200. """
  201. Test whether the mouse event occurred in the Tick marks.
  202. This function always returns false. It is more useful to test if the
  203. axis as a whole contains the mouse rather than the set of tick marks.
  204. """
  205. inside, info = self._default_contains(mouseevent)
  206. if inside is not None:
  207. return inside, info
  208. return False, {}
  209. def set_pad(self, val):
  210. """
  211. Set the tick label pad in points
  212. Parameters
  213. ----------
  214. val : float
  215. """
  216. self._apply_params(pad=val)
  217. self.stale = True
  218. def get_pad(self):
  219. 'Get the value of the tick label pad in points'
  220. return self._base_pad
  221. def _get_text1(self):
  222. 'Get the default Text 1 instance'
  223. pass
  224. def _get_text2(self):
  225. 'Get the default Text 2 instance'
  226. pass
  227. def _get_tick1line(self):
  228. 'Get the default line2D instance for tick1'
  229. pass
  230. def _get_tick2line(self):
  231. 'Get the default line2D instance for tick2'
  232. pass
  233. def _get_gridline(self):
  234. 'Get the default grid Line2d instance for this tick'
  235. pass
  236. def get_loc(self):
  237. 'Return the tick location (data coords) as a scalar'
  238. return self._loc
  239. @martist.allow_rasterization
  240. def draw(self, renderer):
  241. if not self.get_visible():
  242. self.stale = False
  243. return
  244. renderer.open_group(self.__name__, gid=self.get_gid())
  245. for artist in [self.gridline, self.tick1line, self.tick2line,
  246. self.label1, self.label2]:
  247. artist.draw(renderer)
  248. renderer.close_group(self.__name__)
  249. self.stale = False
  250. def set_label1(self, s):
  251. """
  252. Set the label1 text.
  253. Parameters
  254. ----------
  255. s : str
  256. """
  257. self.label1.set_text(s)
  258. self.stale = True
  259. set_label = set_label1
  260. def set_label2(self, s):
  261. """
  262. Set the label2 text.
  263. Parameters
  264. ----------
  265. s : str
  266. """
  267. self.label2.set_text(s)
  268. self.stale = True
  269. def _set_artist_props(self, a):
  270. a.set_figure(self.figure)
  271. def get_view_interval(self):
  272. """
  273. Return the view limits ``(min, max)`` of the axis the tick belongs to.
  274. """
  275. raise NotImplementedError('Derived must override')
  276. def _apply_params(self, **kw):
  277. for name, target in [("gridOn", self.gridline),
  278. ("tick1On", self.tick1line),
  279. ("tick2On", self.tick2line),
  280. ("label1On", self.label1),
  281. ("label2On", self.label2)]:
  282. if name in kw:
  283. target.set_visible(kw.pop(name))
  284. if any(k in kw for k in ['size', 'width', 'pad', 'tickdir']):
  285. self._size = kw.pop('size', self._size)
  286. # Width could be handled outside this block, but it is
  287. # convenient to leave it here.
  288. self._width = kw.pop('width', self._width)
  289. self._base_pad = kw.pop('pad', self._base_pad)
  290. # apply_tickdir uses _size and _base_pad to make _pad,
  291. # and also makes _tickmarkers.
  292. self.apply_tickdir(kw.pop('tickdir', self._tickdir))
  293. self.tick1line.set_marker(self._tickmarkers[0])
  294. self.tick2line.set_marker(self._tickmarkers[1])
  295. for line in (self.tick1line, self.tick2line):
  296. line.set_markersize(self._size)
  297. line.set_markeredgewidth(self._width)
  298. # _get_text1_transform uses _pad from apply_tickdir.
  299. trans = self._get_text1_transform()[0]
  300. self.label1.set_transform(trans)
  301. trans = self._get_text2_transform()[0]
  302. self.label2.set_transform(trans)
  303. tick_kw = {k: v for k, v in kw.items() if k in ['color', 'zorder']}
  304. self.tick1line.set(**tick_kw)
  305. self.tick2line.set(**tick_kw)
  306. for k, v in tick_kw.items():
  307. setattr(self, '_' + k, v)
  308. if 'labelrotation' in kw:
  309. self._set_labelrotation(kw.pop('labelrotation'))
  310. self.label1.set(rotation=self._labelrotation[1])
  311. self.label2.set(rotation=self._labelrotation[1])
  312. label_kw = {k[5:]: v for k, v in kw.items()
  313. if k in ['labelsize', 'labelcolor']}
  314. self.label1.set(**label_kw)
  315. self.label2.set(**label_kw)
  316. for k, v in label_kw.items():
  317. # for labelsize the text objects covert str ('small')
  318. # -> points. grab the integer from the `Text` object
  319. # instead of saving the string representation
  320. v = getattr(self.label1, 'get_' + k)()
  321. setattr(self, '_label' + k, v)
  322. grid_kw = {k[5:]: v for k, v in kw.items()
  323. if k in _gridline_param_names}
  324. self.gridline.set(**grid_kw)
  325. for k, v in grid_kw.items():
  326. setattr(self, '_grid_' + k, v)
  327. def update_position(self, loc):
  328. 'Set the location of tick in data coords with scalar *loc*'
  329. raise NotImplementedError('Derived must override')
  330. def _get_text1_transform(self):
  331. raise NotImplementedError('Derived must override')
  332. def _get_text2_transform(self):
  333. raise NotImplementedError('Derived must override')
  334. class XTick(Tick):
  335. """
  336. Contains all the Artists needed to make an x tick - the tick line,
  337. the label text and the grid line
  338. """
  339. __name__ = 'xtick'
  340. def _get_text1_transform(self):
  341. return self.axes.get_xaxis_text1_transform(self._pad)
  342. def _get_text2_transform(self):
  343. return self.axes.get_xaxis_text2_transform(self._pad)
  344. def apply_tickdir(self, tickdir):
  345. if tickdir is None:
  346. tickdir = rcParams['%s.direction' % self.__name__.lower()]
  347. self._tickdir = tickdir
  348. if self._tickdir == 'in':
  349. self._tickmarkers = (mlines.TICKUP, mlines.TICKDOWN)
  350. elif self._tickdir == 'inout':
  351. self._tickmarkers = ('|', '|')
  352. else:
  353. self._tickmarkers = (mlines.TICKDOWN, mlines.TICKUP)
  354. self._pad = self._base_pad + self.get_tick_padding()
  355. self.stale = True
  356. def _get_text1(self):
  357. 'Get the default Text instance'
  358. # the y loc is 3 points below the min of y axis
  359. # get the affine as an a, b, c, d, tx, ty list
  360. # x in data coords, y in axes coords
  361. trans, vert, horiz = self._get_text1_transform()
  362. t = mtext.Text(
  363. x=0, y=0,
  364. fontproperties=font_manager.FontProperties(size=self._labelsize),
  365. color=self._labelcolor,
  366. verticalalignment=vert,
  367. horizontalalignment=horiz,
  368. )
  369. t.set_transform(trans)
  370. self._set_artist_props(t)
  371. return t
  372. def _get_text2(self):
  373. 'Get the default Text 2 instance'
  374. # x in data coords, y in axes coords
  375. trans, vert, horiz = self._get_text2_transform()
  376. t = mtext.Text(
  377. x=0, y=1,
  378. fontproperties=font_manager.FontProperties(size=self._labelsize),
  379. color=self._labelcolor,
  380. verticalalignment=vert,
  381. horizontalalignment=horiz,
  382. )
  383. t.set_transform(trans)
  384. self._set_artist_props(t)
  385. return t
  386. def _get_tick1line(self):
  387. 'Get the default line2D instance'
  388. # x in data coords, y in axes coords
  389. l = mlines.Line2D(xdata=(0,), ydata=(0,), color=self._color,
  390. linestyle='None', marker=self._tickmarkers[0],
  391. markersize=self._size,
  392. markeredgewidth=self._width, zorder=self._zorder)
  393. l.set_transform(self.axes.get_xaxis_transform(which='tick1'))
  394. self._set_artist_props(l)
  395. return l
  396. def _get_tick2line(self):
  397. 'Get the default line2D instance'
  398. # x in data coords, y in axes coords
  399. l = mlines.Line2D(xdata=(0,), ydata=(1,),
  400. color=self._color,
  401. linestyle='None',
  402. marker=self._tickmarkers[1],
  403. markersize=self._size,
  404. markeredgewidth=self._width,
  405. zorder=self._zorder)
  406. l.set_transform(self.axes.get_xaxis_transform(which='tick2'))
  407. self._set_artist_props(l)
  408. return l
  409. def _get_gridline(self):
  410. 'Get the default line2D instance'
  411. # x in data coords, y in axes coords
  412. l = mlines.Line2D(xdata=(0.0, 0.0), ydata=(0, 1.0),
  413. color=self._grid_color,
  414. linestyle=self._grid_linestyle,
  415. linewidth=self._grid_linewidth,
  416. alpha=self._grid_alpha,
  417. markersize=0,
  418. **self._grid_kw)
  419. l.set_transform(self.axes.get_xaxis_transform(which='grid'))
  420. l.get_path()._interpolation_steps = GRIDLINE_INTERPOLATION_STEPS
  421. self._set_artist_props(l)
  422. return l
  423. def update_position(self, loc):
  424. """Set the location of tick in data coords with scalar *loc*."""
  425. self.tick1line.set_xdata((loc,))
  426. self.tick2line.set_xdata((loc,))
  427. self.gridline.set_xdata((loc,))
  428. self.label1.set_x(loc)
  429. self.label2.set_x(loc)
  430. self._loc = loc
  431. self.stale = True
  432. def get_view_interval(self):
  433. # docstring inherited
  434. return self.axes.viewLim.intervalx
  435. class YTick(Tick):
  436. """
  437. Contains all the Artists needed to make a Y tick - the tick line,
  438. the label text and the grid line
  439. """
  440. __name__ = 'ytick'
  441. def _get_text1_transform(self):
  442. return self.axes.get_yaxis_text1_transform(self._pad)
  443. def _get_text2_transform(self):
  444. return self.axes.get_yaxis_text2_transform(self._pad)
  445. def apply_tickdir(self, tickdir):
  446. if tickdir is None:
  447. tickdir = rcParams['%s.direction' % self.__name__.lower()]
  448. self._tickdir = tickdir
  449. if self._tickdir == 'in':
  450. self._tickmarkers = (mlines.TICKRIGHT, mlines.TICKLEFT)
  451. elif self._tickdir == 'inout':
  452. self._tickmarkers = ('_', '_')
  453. else:
  454. self._tickmarkers = (mlines.TICKLEFT, mlines.TICKRIGHT)
  455. self._pad = self._base_pad + self.get_tick_padding()
  456. self.stale = True
  457. # how far from the y axis line the right of the ticklabel are
  458. def _get_text1(self):
  459. 'Get the default Text instance'
  460. # x in axes coords, y in data coords
  461. trans, vert, horiz = self._get_text1_transform()
  462. t = mtext.Text(
  463. x=0, y=0,
  464. fontproperties=font_manager.FontProperties(size=self._labelsize),
  465. color=self._labelcolor,
  466. verticalalignment=vert,
  467. horizontalalignment=horiz,
  468. )
  469. t.set_transform(trans)
  470. self._set_artist_props(t)
  471. return t
  472. def _get_text2(self):
  473. 'Get the default Text instance'
  474. # x in axes coords, y in data coords
  475. trans, vert, horiz = self._get_text2_transform()
  476. t = mtext.Text(
  477. x=1, y=0,
  478. fontproperties=font_manager.FontProperties(size=self._labelsize),
  479. color=self._labelcolor,
  480. verticalalignment=vert,
  481. horizontalalignment=horiz,
  482. )
  483. t.set_transform(trans)
  484. self._set_artist_props(t)
  485. return t
  486. def _get_tick1line(self):
  487. 'Get the default line2D instance'
  488. # x in axes coords, y in data coords
  489. l = mlines.Line2D((0,), (0,),
  490. color=self._color,
  491. marker=self._tickmarkers[0],
  492. linestyle='None',
  493. markersize=self._size,
  494. markeredgewidth=self._width,
  495. zorder=self._zorder)
  496. l.set_transform(self.axes.get_yaxis_transform(which='tick1'))
  497. self._set_artist_props(l)
  498. return l
  499. def _get_tick2line(self):
  500. 'Get the default line2D instance'
  501. # x in axes coords, y in data coords
  502. l = mlines.Line2D((1,), (0,),
  503. color=self._color,
  504. marker=self._tickmarkers[1],
  505. linestyle='None',
  506. markersize=self._size,
  507. markeredgewidth=self._width,
  508. zorder=self._zorder)
  509. l.set_transform(self.axes.get_yaxis_transform(which='tick2'))
  510. self._set_artist_props(l)
  511. return l
  512. def _get_gridline(self):
  513. 'Get the default line2D instance'
  514. # x in axes coords, y in data coords
  515. l = mlines.Line2D(xdata=(0, 1), ydata=(0, 0),
  516. color=self._grid_color,
  517. linestyle=self._grid_linestyle,
  518. linewidth=self._grid_linewidth,
  519. alpha=self._grid_alpha,
  520. markersize=0,
  521. **self._grid_kw)
  522. l.set_transform(self.axes.get_yaxis_transform(which='grid'))
  523. l.get_path()._interpolation_steps = GRIDLINE_INTERPOLATION_STEPS
  524. self._set_artist_props(l)
  525. return l
  526. def update_position(self, loc):
  527. """Set the location of tick in data coords with scalar *loc*."""
  528. self.tick1line.set_ydata((loc,))
  529. self.tick2line.set_ydata((loc,))
  530. self.gridline.set_ydata((loc,))
  531. self.label1.set_y(loc)
  532. self.label2.set_y(loc)
  533. self._loc = loc
  534. self.stale = True
  535. def get_view_interval(self):
  536. # docstring inherited
  537. return self.axes.viewLim.intervaly
  538. class Ticker:
  539. """
  540. A container for the objects defining tick position and format.
  541. Attributes
  542. ----------
  543. locator : `matplotlib.ticker.Locator` subclass
  544. Determines the positions of the ticks.
  545. formatter : `matplotlib.ticker.Formatter` subclass
  546. Determines the format of the tick labels.
  547. """
  548. def __init__(self):
  549. self._locator = None
  550. self._formatter = None
  551. @property
  552. def locator(self):
  553. return self._locator
  554. @locator.setter
  555. def locator(self, locator):
  556. if not isinstance(locator, mticker.Locator):
  557. cbook.warn_deprecated(
  558. "3.2", message="Support for locators that do not subclass "
  559. "matplotlib.ticker.Locator is deprecated since %(since)s and "
  560. "support for them will be removed %(removal)s.")
  561. self._locator = locator
  562. @property
  563. def formatter(self):
  564. return self._formatter
  565. @formatter.setter
  566. def formatter(self, formatter):
  567. if not isinstance(formatter, mticker.Formatter):
  568. cbook.warn_deprecated(
  569. "3.2", message="Support for formatters that do not subclass "
  570. "matplotlib.ticker.Formatter is deprecated since %(since)s "
  571. "and support for them will be removed %(removal)s.")
  572. self._formatter = formatter
  573. class _LazyTickList:
  574. """
  575. A descriptor for lazy instantiation of tick lists.
  576. See comment above definition of the ``majorTicks`` and ``minorTicks``
  577. attributes.
  578. """
  579. def __init__(self, major):
  580. self._major = major
  581. def __get__(self, instance, cls):
  582. if instance is None:
  583. return self
  584. else:
  585. # instance._get_tick() can itself try to access the majorTicks
  586. # attribute (e.g. in certain projection classes which override
  587. # e.g. get_xaxis_text1_transform). In order to avoid infinite
  588. # recursion, first set the majorTicks on the instance to an empty
  589. # list, then create the tick and append it.
  590. if self._major:
  591. instance.majorTicks = []
  592. tick = instance._get_tick(major=True)
  593. instance.majorTicks.append(tick)
  594. return instance.majorTicks
  595. else:
  596. instance.minorTicks = []
  597. tick = instance._get_tick(major=False)
  598. instance.minorTicks.append(tick)
  599. return instance.minorTicks
  600. class Axis(martist.Artist):
  601. """
  602. Base class for `.XAxis` and `.YAxis`.
  603. Attributes
  604. ----------
  605. isDefault_label : bool
  606. axes : `matplotlib.axes.Axes`
  607. The `~.axes.Axes` to which the Axis belongs.
  608. major : `matplotlib.axis.Ticker`
  609. Determines the major tick positions and their label format.
  610. minor : `matplotlib.axis.Ticker`
  611. Determines the minor tick positions and their label format.
  612. callbacks : `matplotlib.cbook.CallbackRegistry`
  613. label : `.Text`
  614. The axis label.
  615. labelpad : float
  616. The distance between the axis label and the tick labels.
  617. Defaults to :rc:`axes.labelpad` = 4.
  618. offsetText : `.Text`
  619. A `.Text` object containing the data offset of the ticks (if any).
  620. pickradius : float
  621. The acceptance radius for containment tests. See also `.Axis.contains`.
  622. majorTicks : list of `.Tick`
  623. The major ticks.
  624. minorTicks : list of `.Tick`
  625. The minor ticks.
  626. """
  627. OFFSETTEXTPAD = 3
  628. def __str__(self):
  629. return "{}({},{})".format(
  630. type(self).__name__, *self.axes.transAxes.transform((0, 0)))
  631. def __init__(self, axes, pickradius=15):
  632. """
  633. Parameters
  634. ----------
  635. axes : `matplotlib.axes.Axes`
  636. The `~.axes.Axes` to which the created Axis belongs.
  637. pickradius : float
  638. The acceptance radius for containment tests. See also
  639. `.Axis.contains`.
  640. """
  641. martist.Artist.__init__(self)
  642. self._remove_overlapping_locs = True
  643. self.set_figure(axes.figure)
  644. self.isDefault_label = True
  645. self.axes = axes
  646. self.major = Ticker()
  647. self.minor = Ticker()
  648. self.callbacks = cbook.CallbackRegistry()
  649. self._autolabelpos = True
  650. self._smart_bounds = False # Deprecated in 3.2
  651. self.label = self._get_label()
  652. self.labelpad = rcParams['axes.labelpad']
  653. self.offsetText = self._get_offset_text()
  654. self.pickradius = pickradius
  655. # Initialize here for testing; later add API
  656. self._major_tick_kw = dict()
  657. self._minor_tick_kw = dict()
  658. self.cla()
  659. self._set_scale('linear')
  660. # During initialization, Axis objects often create ticks that are later
  661. # unused; this turns out to be a very slow step. Instead, use a custom
  662. # descriptor to make the tick lists lazy and instantiate them as needed.
  663. majorTicks = _LazyTickList(major=True)
  664. minorTicks = _LazyTickList(major=False)
  665. def get_remove_overlapping_locs(self):
  666. return self._remove_overlapping_locs
  667. def set_remove_overlapping_locs(self, val):
  668. self._remove_overlapping_locs = bool(val)
  669. remove_overlapping_locs = property(
  670. get_remove_overlapping_locs, set_remove_overlapping_locs,
  671. doc=('If minor ticker locations that overlap with major '
  672. 'ticker locations should be trimmed.'))
  673. def set_label_coords(self, x, y, transform=None):
  674. """
  675. Set the coordinates of the label.
  676. By default, the x coordinate of the y label and the y coordinate of the
  677. x label are determined by the tick label bounding boxes, but this can
  678. lead to poor alignment of multiple labels if there are multiple axes.
  679. You can also specify the coordinate system of the label with the
  680. transform. If None, the default coordinate system will be the axes
  681. coordinate system: (0, 0) is bottom left, (0.5, 0.5) is center, etc.
  682. """
  683. self._autolabelpos = False
  684. if transform is None:
  685. transform = self.axes.transAxes
  686. self.label.set_transform(transform)
  687. self.label.set_position((x, y))
  688. self.stale = True
  689. def get_transform(self):
  690. return self._scale.get_transform()
  691. def get_scale(self):
  692. return self._scale.name
  693. def _set_scale(self, value, **kwargs):
  694. self._scale = mscale.scale_factory(value, self, **kwargs)
  695. self._scale.set_default_locators_and_formatters(self)
  696. self.isDefault_majloc = True
  697. self.isDefault_minloc = True
  698. self.isDefault_majfmt = True
  699. self.isDefault_minfmt = True
  700. def limit_range_for_scale(self, vmin, vmax):
  701. return self._scale.limit_range_for_scale(vmin, vmax, self.get_minpos())
  702. def get_children(self):
  703. return [self.label, self.offsetText,
  704. *self.get_major_ticks(), *self.get_minor_ticks()]
  705. def cla(self):
  706. """Clear this axis."""
  707. self.label.set_text('') # self.set_label_text would change isDefault_
  708. self._set_scale('linear')
  709. # Clear the callback registry for this axis, or it may "leak"
  710. self.callbacks = cbook.CallbackRegistry()
  711. # whether the grids are on
  712. self._gridOnMajor = (rcParams['axes.grid'] and
  713. rcParams['axes.grid.which'] in ('both', 'major'))
  714. self._gridOnMinor = (rcParams['axes.grid'] and
  715. rcParams['axes.grid.which'] in ('both', 'minor'))
  716. self.reset_ticks()
  717. self.converter = None
  718. self.units = None
  719. self.set_units(None)
  720. self.stale = True
  721. def reset_ticks(self):
  722. """
  723. Re-initialize the major and minor Tick lists.
  724. Each list starts with a single fresh Tick.
  725. """
  726. # Restore the lazy tick lists.
  727. try:
  728. del self.majorTicks
  729. except AttributeError:
  730. pass
  731. try:
  732. del self.minorTicks
  733. except AttributeError:
  734. pass
  735. try:
  736. self.set_clip_path(self.axes.patch)
  737. except AttributeError:
  738. pass
  739. def set_tick_params(self, which='major', reset=False, **kw):
  740. """
  741. Set appearance parameters for ticks, ticklabels, and gridlines.
  742. For documentation of keyword arguments, see
  743. :meth:`matplotlib.axes.Axes.tick_params`.
  744. """
  745. dicts = []
  746. if which == 'major' or which == 'both':
  747. dicts.append(self._major_tick_kw)
  748. if which == 'minor' or which == 'both':
  749. dicts.append(self._minor_tick_kw)
  750. kwtrans = self._translate_tick_kw(kw)
  751. # this stashes the parameter changes so any new ticks will
  752. # automatically get them
  753. for d in dicts:
  754. if reset:
  755. d.clear()
  756. d.update(kwtrans)
  757. if reset:
  758. self.reset_ticks()
  759. else:
  760. # apply the new kwargs to the existing ticks
  761. if which == 'major' or which == 'both':
  762. for tick in self.majorTicks:
  763. tick._apply_params(**kwtrans)
  764. if which == 'minor' or which == 'both':
  765. for tick in self.minorTicks:
  766. tick._apply_params(**kwtrans)
  767. # special-case label color to also apply to the offset
  768. # text
  769. if 'labelcolor' in kwtrans:
  770. self.offsetText.set_color(kwtrans['labelcolor'])
  771. self.stale = True
  772. @staticmethod
  773. def _translate_tick_kw(kw):
  774. # The following lists may be moved to a more accessible location.
  775. kwkeys = ['size', 'width', 'color', 'tickdir', 'pad',
  776. 'labelsize', 'labelcolor', 'zorder', 'gridOn',
  777. 'tick1On', 'tick2On', 'label1On', 'label2On',
  778. 'length', 'direction', 'left', 'bottom', 'right', 'top',
  779. 'labelleft', 'labelbottom', 'labelright', 'labeltop',
  780. 'labelrotation'] + _gridline_param_names
  781. kwtrans = {}
  782. if 'length' in kw:
  783. kwtrans['size'] = kw.pop('length')
  784. if 'direction' in kw:
  785. kwtrans['tickdir'] = kw.pop('direction')
  786. if 'rotation' in kw:
  787. kwtrans['labelrotation'] = kw.pop('rotation')
  788. if 'left' in kw:
  789. kwtrans['tick1On'] = kw.pop('left')
  790. if 'bottom' in kw:
  791. kwtrans['tick1On'] = kw.pop('bottom')
  792. if 'right' in kw:
  793. kwtrans['tick2On'] = kw.pop('right')
  794. if 'top' in kw:
  795. kwtrans['tick2On'] = kw.pop('top')
  796. if 'labelleft' in kw:
  797. kwtrans['label1On'] = kw.pop('labelleft')
  798. if 'labelbottom' in kw:
  799. kwtrans['label1On'] = kw.pop('labelbottom')
  800. if 'labelright' in kw:
  801. kwtrans['label2On'] = kw.pop('labelright')
  802. if 'labeltop' in kw:
  803. kwtrans['label2On'] = kw.pop('labeltop')
  804. if 'colors' in kw:
  805. c = kw.pop('colors')
  806. kwtrans['color'] = c
  807. kwtrans['labelcolor'] = c
  808. # Maybe move the checking up to the caller of this method.
  809. for key in kw:
  810. if key not in kwkeys:
  811. raise ValueError(
  812. "keyword %s is not recognized; valid keywords are %s"
  813. % (key, kwkeys))
  814. kwtrans.update(kw)
  815. return kwtrans
  816. def set_clip_path(self, clippath, transform=None):
  817. martist.Artist.set_clip_path(self, clippath, transform)
  818. for child in self.majorTicks + self.minorTicks:
  819. child.set_clip_path(clippath, transform)
  820. self.stale = True
  821. def get_view_interval(self):
  822. """Return the view limits ``(min, max)`` of this axis."""
  823. raise NotImplementedError('Derived must override')
  824. def set_view_interval(self, vmin, vmax, ignore=False):
  825. """
  826. Set the axis view limits. This method is for internal use; Matplotlib
  827. users should typically use e.g. `~Axes.set_xlim` and `~Axes.set_ylim`.
  828. If *ignore* is False (the default), this method will never reduce the
  829. preexisting view limits, only expand them if *vmin* or *vmax* are not
  830. within them. Moreover, the order of *vmin* and *vmax* does not matter;
  831. the orientation of the axis will not change.
  832. If *ignore* is True, the view limits will be set exactly to ``(vmin,
  833. vmax)`` in that order.
  834. """
  835. raise NotImplementedError('Derived must override')
  836. def get_data_interval(self):
  837. """Return the Interval instance for this axis data limits."""
  838. raise NotImplementedError('Derived must override')
  839. def set_data_interval(self, vmin, vmax, ignore=False):
  840. """
  841. Set the axis data limits. This method is for internal use.
  842. If *ignore* is False (the default), this method will never reduce the
  843. preexisting data limits, only expand them if *vmin* or *vmax* are not
  844. within them. Moreover, the order of *vmin* and *vmax* does not matter;
  845. the orientation of the axis will not change.
  846. If *ignore* is True, the data limits will be set exactly to ``(vmin,
  847. vmax)`` in that order.
  848. """
  849. raise NotImplementedError('Derived must override')
  850. def get_inverted(self):
  851. """
  852. Return whether the axis is oriented in the "inverse" direction.
  853. The "normal" direction is increasing to the right for the x-axis and to
  854. the top for the y-axis; the "inverse" direction is increasing to the
  855. left for the x-axis and to the bottom for the y-axis.
  856. """
  857. low, high = self.get_view_interval()
  858. return high < low
  859. def set_inverted(self, inverted):
  860. """
  861. Set whether the axis is oriented in the "inverse" direction.
  862. The "normal" direction is increasing to the right for the x-axis and to
  863. the top for the y-axis; the "inverse" direction is increasing to the
  864. left for the x-axis and to the bottom for the y-axis.
  865. """
  866. # Currently, must be implemented in subclasses using set_xlim/set_ylim
  867. # rather than generically using set_view_interval, so that shared
  868. # axes get updated as well.
  869. raise NotImplementedError('Derived must override')
  870. def set_default_intervals(self):
  871. """
  872. Set the default limits for the axis data and view interval if they
  873. have not been not mutated yet.
  874. """
  875. # this is mainly in support of custom object plotting. For
  876. # example, if someone passes in a datetime object, we do not
  877. # know automagically how to set the default min/max of the
  878. # data and view limits. The unit conversion AxisInfo
  879. # interface provides a hook for custom types to register
  880. # default limits through the AxisInfo.default_limits
  881. # attribute, and the derived code below will check for that
  882. # and use it if is available (else just use 0..1)
  883. def _set_artist_props(self, a):
  884. if a is None:
  885. return
  886. a.set_figure(self.figure)
  887. @cbook.deprecated("3.1")
  888. def iter_ticks(self):
  889. """
  890. Yield ``(Tick, location, label)`` tuples for major and minor ticks.
  891. """
  892. major_locs = self.get_majorticklocs()
  893. major_labels = self.major.formatter.format_ticks(major_locs)
  894. major_ticks = self.get_major_ticks(len(major_locs))
  895. yield from zip(major_ticks, major_locs, major_labels)
  896. minor_locs = self.get_minorticklocs()
  897. minor_labels = self.minor.formatter.format_ticks(minor_locs)
  898. minor_ticks = self.get_minor_ticks(len(minor_locs))
  899. yield from zip(minor_ticks, minor_locs, minor_labels)
  900. def get_ticklabel_extents(self, renderer):
  901. """
  902. Get the extents of the tick labels on either side
  903. of the axes.
  904. """
  905. ticks_to_draw = self._update_ticks()
  906. ticklabelBoxes, ticklabelBoxes2 = self._get_tick_bboxes(ticks_to_draw,
  907. renderer)
  908. if len(ticklabelBoxes):
  909. bbox = mtransforms.Bbox.union(ticklabelBoxes)
  910. else:
  911. bbox = mtransforms.Bbox.from_extents(0, 0, 0, 0)
  912. if len(ticklabelBoxes2):
  913. bbox2 = mtransforms.Bbox.union(ticklabelBoxes2)
  914. else:
  915. bbox2 = mtransforms.Bbox.from_extents(0, 0, 0, 0)
  916. return bbox, bbox2
  917. @cbook.deprecated("3.2")
  918. def set_smart_bounds(self, value):
  919. """Set the axis to have smart bounds."""
  920. self._smart_bounds = value
  921. self.stale = True
  922. @cbook.deprecated("3.2")
  923. def get_smart_bounds(self):
  924. """Return whether the axis has smart bounds."""
  925. return self._smart_bounds
  926. def _update_ticks(self):
  927. """
  928. Update ticks (position and labels) using the current data interval of
  929. the axes. Return the list of ticks that will be drawn.
  930. """
  931. major_locs = self.get_majorticklocs()
  932. major_labels = self.major.formatter.format_ticks(major_locs)
  933. major_ticks = self.get_major_ticks(len(major_locs))
  934. self.major.formatter.set_locs(major_locs)
  935. for tick, loc, label in zip(major_ticks, major_locs, major_labels):
  936. tick.update_position(loc)
  937. tick.set_label1(label)
  938. tick.set_label2(label)
  939. minor_locs = self.get_minorticklocs()
  940. minor_labels = self.minor.formatter.format_ticks(minor_locs)
  941. minor_ticks = self.get_minor_ticks(len(minor_locs))
  942. self.minor.formatter.set_locs(minor_locs)
  943. for tick, loc, label in zip(minor_ticks, minor_locs, minor_labels):
  944. tick.update_position(loc)
  945. tick.set_label1(label)
  946. tick.set_label2(label)
  947. ticks = [*major_ticks, *minor_ticks]
  948. view_low, view_high = self.get_view_interval()
  949. if view_low > view_high:
  950. view_low, view_high = view_high, view_low
  951. if self._smart_bounds and ticks: # _smart_bounds is deprecated in 3.2
  952. # handle inverted limits
  953. data_low, data_high = sorted(self.get_data_interval())
  954. locs = np.sort([tick.get_loc() for tick in ticks])
  955. if data_low <= view_low:
  956. # data extends beyond view, take view as limit
  957. ilow = view_low
  958. else:
  959. # data stops within view, take best tick
  960. good_locs = locs[locs <= data_low]
  961. if len(good_locs):
  962. # last tick prior or equal to first data point
  963. ilow = good_locs[-1]
  964. else:
  965. # No ticks (why not?), take first tick
  966. ilow = locs[0]
  967. if data_high >= view_high:
  968. # data extends beyond view, take view as limit
  969. ihigh = view_high
  970. else:
  971. # data stops within view, take best tick
  972. good_locs = locs[locs >= data_high]
  973. if len(good_locs):
  974. # first tick after or equal to last data point
  975. ihigh = good_locs[0]
  976. else:
  977. # No ticks (why not?), take last tick
  978. ihigh = locs[-1]
  979. ticks = [tick for tick in ticks if ilow <= tick.get_loc() <= ihigh]
  980. interval_t = self.get_transform().transform([view_low, view_high])
  981. ticks_to_draw = []
  982. for tick in ticks:
  983. try:
  984. loc_t = self.get_transform().transform(tick.get_loc())
  985. except AssertionError:
  986. # transforms.transform doesn't allow masked values but
  987. # some scales might make them, so we need this try/except.
  988. pass
  989. else:
  990. if mtransforms._interval_contains_close(interval_t, loc_t):
  991. ticks_to_draw.append(tick)
  992. return ticks_to_draw
  993. def _get_tick_bboxes(self, ticks, renderer):
  994. """Return lists of bboxes for ticks' label1's and label2's."""
  995. return ([tick.label1.get_window_extent(renderer)
  996. for tick in ticks if tick.label1.get_visible()],
  997. [tick.label2.get_window_extent(renderer)
  998. for tick in ticks if tick.label2.get_visible()])
  999. def get_tightbbox(self, renderer):
  1000. """
  1001. Return a bounding box that encloses the axis. It only accounts
  1002. tick labels, axis label, and offsetText.
  1003. """
  1004. if not self.get_visible():
  1005. return
  1006. ticks_to_draw = self._update_ticks()
  1007. self._update_label_position(renderer)
  1008. # go back to just this axis's tick labels
  1009. ticklabelBoxes, ticklabelBoxes2 = self._get_tick_bboxes(
  1010. ticks_to_draw, renderer)
  1011. self._update_offset_text_position(ticklabelBoxes, ticklabelBoxes2)
  1012. self.offsetText.set_text(self.major.formatter.get_offset())
  1013. bboxes = [
  1014. *(a.get_window_extent(renderer)
  1015. for a in [self.label, self.offsetText]
  1016. if a.get_visible()),
  1017. *ticklabelBoxes,
  1018. *ticklabelBoxes2,
  1019. ]
  1020. bboxes = [b for b in bboxes
  1021. if 0 < b.width < np.inf and 0 < b.height < np.inf]
  1022. if bboxes:
  1023. return mtransforms.Bbox.union(bboxes)
  1024. else:
  1025. return None
  1026. def get_tick_padding(self):
  1027. values = []
  1028. if len(self.majorTicks):
  1029. values.append(self.majorTicks[0].get_tick_padding())
  1030. if len(self.minorTicks):
  1031. values.append(self.minorTicks[0].get_tick_padding())
  1032. return max(values, default=0)
  1033. @martist.allow_rasterization
  1034. def draw(self, renderer, *args, **kwargs):
  1035. 'Draw the axis lines, grid lines, tick lines and labels'
  1036. if not self.get_visible():
  1037. return
  1038. renderer.open_group(__name__, gid=self.get_gid())
  1039. ticks_to_draw = self._update_ticks()
  1040. ticklabelBoxes, ticklabelBoxes2 = self._get_tick_bboxes(ticks_to_draw,
  1041. renderer)
  1042. for tick in ticks_to_draw:
  1043. tick.draw(renderer)
  1044. # scale up the axis label box to also find the neighbors, not
  1045. # just the tick labels that actually overlap note we need a
  1046. # *copy* of the axis label box because we don't wan't to scale
  1047. # the actual bbox
  1048. self._update_label_position(renderer)
  1049. self.label.draw(renderer)
  1050. self._update_offset_text_position(ticklabelBoxes, ticklabelBoxes2)
  1051. self.offsetText.set_text(self.major.formatter.get_offset())
  1052. self.offsetText.draw(renderer)
  1053. renderer.close_group(__name__)
  1054. self.stale = False
  1055. def _get_label(self):
  1056. raise NotImplementedError('Derived must override')
  1057. def _get_offset_text(self):
  1058. raise NotImplementedError('Derived must override')
  1059. def get_gridlines(self):
  1060. 'Return the grid lines as a list of Line2D instance'
  1061. ticks = self.get_major_ticks()
  1062. return cbook.silent_list('Line2D gridline',
  1063. [tick.gridline for tick in ticks])
  1064. def get_label(self):
  1065. 'Return the axis label as a Text instance'
  1066. return self.label
  1067. def get_offset_text(self):
  1068. 'Return the axis offsetText as a Text instance'
  1069. return self.offsetText
  1070. def get_pickradius(self):
  1071. 'Return the depth of the axis used by the picker'
  1072. return self.pickradius
  1073. def get_majorticklabels(self):
  1074. 'Return a list of Text instances for the major ticklabels.'
  1075. ticks = self.get_major_ticks()
  1076. labels1 = [tick.label1 for tick in ticks if tick.label1.get_visible()]
  1077. labels2 = [tick.label2 for tick in ticks if tick.label2.get_visible()]
  1078. return cbook.silent_list('Text major ticklabel', labels1 + labels2)
  1079. def get_minorticklabels(self):
  1080. 'Return a list of Text instances for the minor ticklabels.'
  1081. ticks = self.get_minor_ticks()
  1082. labels1 = [tick.label1 for tick in ticks if tick.label1.get_visible()]
  1083. labels2 = [tick.label2 for tick in ticks if tick.label2.get_visible()]
  1084. return cbook.silent_list('Text minor ticklabel', labels1 + labels2)
  1085. def get_ticklabels(self, minor=False, which=None):
  1086. """
  1087. Get the tick labels as a list of `~matplotlib.text.Text` instances.
  1088. Parameters
  1089. ----------
  1090. minor : bool
  1091. If True return the minor ticklabels,
  1092. else return the major ticklabels
  1093. which : None, ('minor', 'major', 'both')
  1094. Overrides *minor*.
  1095. Selects which ticklabels to return
  1096. Returns
  1097. -------
  1098. ret : list
  1099. List of `~matplotlib.text.Text` instances.
  1100. """
  1101. if which is not None:
  1102. if which == 'minor':
  1103. return self.get_minorticklabels()
  1104. elif which == 'major':
  1105. return self.get_majorticklabels()
  1106. elif which == 'both':
  1107. return self.get_majorticklabels() + self.get_minorticklabels()
  1108. else:
  1109. cbook._check_in_list(['major', 'minor', 'both'], which=which)
  1110. if minor:
  1111. return self.get_minorticklabels()
  1112. return self.get_majorticklabels()
  1113. def get_majorticklines(self):
  1114. 'Return the major tick lines as a list of Line2D instances'
  1115. lines = []
  1116. ticks = self.get_major_ticks()
  1117. for tick in ticks:
  1118. lines.append(tick.tick1line)
  1119. lines.append(tick.tick2line)
  1120. return cbook.silent_list('Line2D ticklines', lines)
  1121. def get_minorticklines(self):
  1122. 'Return the minor tick lines as a list of Line2D instances'
  1123. lines = []
  1124. ticks = self.get_minor_ticks()
  1125. for tick in ticks:
  1126. lines.append(tick.tick1line)
  1127. lines.append(tick.tick2line)
  1128. return cbook.silent_list('Line2D ticklines', lines)
  1129. def get_ticklines(self, minor=False):
  1130. 'Return the tick lines as a list of Line2D instances'
  1131. if minor:
  1132. return self.get_minorticklines()
  1133. return self.get_majorticklines()
  1134. def get_majorticklocs(self):
  1135. """Get the array of major tick locations in data coordinates."""
  1136. return self.major.locator()
  1137. def get_minorticklocs(self):
  1138. """Get the array of minor tick locations in data coordinates."""
  1139. # Remove minor ticks duplicating major ticks.
  1140. major_locs = self.major.locator()
  1141. minor_locs = self.minor.locator()
  1142. transform = self._scale.get_transform()
  1143. tr_minor_locs = transform.transform(minor_locs)
  1144. tr_major_locs = transform.transform(major_locs)
  1145. lo, hi = sorted(transform.transform(self.get_view_interval()))
  1146. # Use the transformed view limits as scale. 1e-5 is the default rtol
  1147. # for np.isclose.
  1148. tol = (hi - lo) * 1e-5
  1149. if self.remove_overlapping_locs:
  1150. minor_locs = [
  1151. loc for loc, tr_loc in zip(minor_locs, tr_minor_locs)
  1152. if ~np.isclose(tr_loc, tr_major_locs, atol=tol, rtol=0).any()]
  1153. return minor_locs
  1154. def get_ticklocs(self, minor=False):
  1155. """Get the array of tick locations in data coordinates."""
  1156. return self.get_minorticklocs() if minor else self.get_majorticklocs()
  1157. def get_ticks_direction(self, minor=False):
  1158. """
  1159. Get the tick directions as a numpy array
  1160. Parameters
  1161. ----------
  1162. minor : boolean
  1163. True to return the minor tick directions,
  1164. False to return the major tick directions,
  1165. Default is False
  1166. Returns
  1167. -------
  1168. numpy array of tick directions
  1169. """
  1170. if minor:
  1171. return np.array(
  1172. [tick._tickdir for tick in self.get_minor_ticks()])
  1173. else:
  1174. return np.array(
  1175. [tick._tickdir for tick in self.get_major_ticks()])
  1176. def _get_tick(self, major):
  1177. """Return the default tick instance."""
  1178. raise NotImplementedError('derived must override')
  1179. def _copy_tick_props(self, src, dest):
  1180. """Copy the properties from *src* tick to *dest* tick."""
  1181. if src is None or dest is None:
  1182. return
  1183. dest.label1.update_from(src.label1)
  1184. dest.label2.update_from(src.label2)
  1185. dest.tick1line.update_from(src.tick1line)
  1186. dest.tick2line.update_from(src.tick2line)
  1187. dest.gridline.update_from(src.gridline)
  1188. def get_label_text(self):
  1189. 'Get the text of the label'
  1190. return self.label.get_text()
  1191. def get_major_locator(self):
  1192. 'Get the locator of the major ticker'
  1193. return self.major.locator
  1194. def get_minor_locator(self):
  1195. 'Get the locator of the minor ticker'
  1196. return self.minor.locator
  1197. def get_major_formatter(self):
  1198. 'Get the formatter of the major ticker'
  1199. return self.major.formatter
  1200. def get_minor_formatter(self):
  1201. 'Get the formatter of the minor ticker'
  1202. return self.minor.formatter
  1203. def get_major_ticks(self, numticks=None):
  1204. 'Get the tick instances; grow as necessary.'
  1205. if numticks is None:
  1206. numticks = len(self.get_majorticklocs())
  1207. while len(self.majorTicks) < numticks:
  1208. # Update the new tick label properties from the old.
  1209. tick = self._get_tick(major=True)
  1210. self.majorTicks.append(tick)
  1211. tick.gridline.set_visible(self._gridOnMajor)
  1212. self._copy_tick_props(self.majorTicks[0], tick)
  1213. return self.majorTicks[:numticks]
  1214. def get_minor_ticks(self, numticks=None):
  1215. 'Get the minor tick instances; grow as necessary.'
  1216. if numticks is None:
  1217. numticks = len(self.get_minorticklocs())
  1218. while len(self.minorTicks) < numticks:
  1219. # Update the new tick label properties from the old.
  1220. tick = self._get_tick(major=False)
  1221. self.minorTicks.append(tick)
  1222. tick.gridline.set_visible(self._gridOnMinor)
  1223. self._copy_tick_props(self.minorTicks[0], tick)
  1224. return self.minorTicks[:numticks]
  1225. def grid(self, b=None, which='major', **kwargs):
  1226. """
  1227. Configure the grid lines.
  1228. Parameters
  1229. ----------
  1230. b : bool or None
  1231. Whether to show the grid lines. If any *kwargs* are supplied,
  1232. it is assumed you want the grid on and *b* will be set to True.
  1233. If *b* is *None* and there are no *kwargs*, this toggles the
  1234. visibility of the lines.
  1235. which : {'major', 'minor', 'both'}
  1236. The grid lines to apply the changes on.
  1237. **kwargs : `.Line2D` properties
  1238. Define the line properties of the grid, e.g.::
  1239. grid(color='r', linestyle='-', linewidth=2)
  1240. """
  1241. if len(kwargs):
  1242. if not b and b is not None: # something false-like but not None
  1243. cbook._warn_external('First parameter to grid() is false, '
  1244. 'but line properties are supplied. The '
  1245. 'grid will be enabled.')
  1246. b = True
  1247. which = which.lower()
  1248. cbook._check_in_list(['major', 'minor', 'both'], which=which)
  1249. gridkw = {'grid_' + item[0]: item[1] for item in kwargs.items()}
  1250. if which in ['minor', 'both']:
  1251. if b is None:
  1252. self._gridOnMinor = not self._gridOnMinor
  1253. else:
  1254. self._gridOnMinor = b
  1255. self.set_tick_params(which='minor', gridOn=self._gridOnMinor,
  1256. **gridkw)
  1257. if which in ['major', 'both']:
  1258. if b is None:
  1259. self._gridOnMajor = not self._gridOnMajor
  1260. else:
  1261. self._gridOnMajor = b
  1262. self.set_tick_params(which='major', gridOn=self._gridOnMajor,
  1263. **gridkw)
  1264. self.stale = True
  1265. def update_units(self, data):
  1266. """
  1267. Introspect *data* for units converter and update the
  1268. axis.converter instance if necessary. Return *True*
  1269. if *data* is registered for unit conversion.
  1270. """
  1271. converter = munits.registry.get_converter(data)
  1272. if converter is None:
  1273. return False
  1274. neednew = self.converter != converter
  1275. self.converter = converter
  1276. default = self.converter.default_units(data, self)
  1277. if default is not None and self.units is None:
  1278. self.set_units(default)
  1279. if neednew:
  1280. self._update_axisinfo()
  1281. self.stale = True
  1282. return True
  1283. def _update_axisinfo(self):
  1284. """
  1285. Check the axis converter for the stored units to see if the
  1286. axis info needs to be updated.
  1287. """
  1288. if self.converter is None:
  1289. return
  1290. info = self.converter.axisinfo(self.units, self)
  1291. if info is None:
  1292. return
  1293. if info.majloc is not None and \
  1294. self.major.locator != info.majloc and self.isDefault_majloc:
  1295. self.set_major_locator(info.majloc)
  1296. self.isDefault_majloc = True
  1297. if info.minloc is not None and \
  1298. self.minor.locator != info.minloc and self.isDefault_minloc:
  1299. self.set_minor_locator(info.minloc)
  1300. self.isDefault_minloc = True
  1301. if info.majfmt is not None and \
  1302. self.major.formatter != info.majfmt and self.isDefault_majfmt:
  1303. self.set_major_formatter(info.majfmt)
  1304. self.isDefault_majfmt = True
  1305. if info.minfmt is not None and \
  1306. self.minor.formatter != info.minfmt and self.isDefault_minfmt:
  1307. self.set_minor_formatter(info.minfmt)
  1308. self.isDefault_minfmt = True
  1309. if info.label is not None and self.isDefault_label:
  1310. self.set_label_text(info.label)
  1311. self.isDefault_label = True
  1312. self.set_default_intervals()
  1313. def have_units(self):
  1314. return self.converter is not None or self.units is not None
  1315. def convert_units(self, x):
  1316. # If x is natively supported by Matplotlib, doesn't need converting
  1317. if munits._is_natively_supported(x):
  1318. return x
  1319. if self.converter is None:
  1320. self.converter = munits.registry.get_converter(x)
  1321. if self.converter is None:
  1322. return x
  1323. try:
  1324. ret = self.converter.convert(x, self.units, self)
  1325. except Exception as e:
  1326. raise munits.ConversionError('Failed to convert value(s) to axis '
  1327. f'units: {x!r}') from e
  1328. return ret
  1329. def set_units(self, u):
  1330. """
  1331. Set the units for axis.
  1332. Parameters
  1333. ----------
  1334. u : units tag
  1335. """
  1336. if u == self.units:
  1337. return
  1338. self.units = u
  1339. self._update_axisinfo()
  1340. self.callbacks.process('units')
  1341. self.callbacks.process('units finalize')
  1342. self.stale = True
  1343. def get_units(self):
  1344. """Return the units for axis."""
  1345. return self.units
  1346. def set_label_text(self, label, fontdict=None, **kwargs):
  1347. """
  1348. Set the text value of the axis label.
  1349. Parameters
  1350. ----------
  1351. label : str
  1352. Text string.
  1353. fontdict : dict
  1354. Text properties.
  1355. **kwargs
  1356. Merged into fontdict.
  1357. """
  1358. self.isDefault_label = False
  1359. self.label.set_text(label)
  1360. if fontdict is not None:
  1361. self.label.update(fontdict)
  1362. self.label.update(kwargs)
  1363. self.stale = True
  1364. return self.label
  1365. def set_major_formatter(self, formatter):
  1366. """
  1367. Set the formatter of the major ticker.
  1368. Parameters
  1369. ----------
  1370. formatter : `~matplotlib.ticker.Formatter`
  1371. """
  1372. cbook._check_isinstance(mticker.Formatter, formatter=formatter)
  1373. self.isDefault_majfmt = False
  1374. self.major.formatter = formatter
  1375. formatter.set_axis(self)
  1376. self.stale = True
  1377. def set_minor_formatter(self, formatter):
  1378. """
  1379. Set the formatter of the minor ticker.
  1380. Parameters
  1381. ----------
  1382. formatter : `~matplotlib.ticker.Formatter`
  1383. """
  1384. cbook._check_isinstance(mticker.Formatter, formatter=formatter)
  1385. self.isDefault_minfmt = False
  1386. self.minor.formatter = formatter
  1387. formatter.set_axis(self)
  1388. self.stale = True
  1389. def set_major_locator(self, locator):
  1390. """
  1391. Set the locator of the major ticker.
  1392. Parameters
  1393. ----------
  1394. locator : `~matplotlib.ticker.Locator`
  1395. """
  1396. cbook._check_isinstance(mticker.Locator, locator=locator)
  1397. self.isDefault_majloc = False
  1398. self.major.locator = locator
  1399. if self.major.formatter:
  1400. self.major.formatter._set_locator(locator)
  1401. locator.set_axis(self)
  1402. self.stale = True
  1403. def set_minor_locator(self, locator):
  1404. """
  1405. Set the locator of the minor ticker.
  1406. Parameters
  1407. ----------
  1408. locator : `~matplotlib.ticker.Locator`
  1409. """
  1410. cbook._check_isinstance(mticker.Locator, locator=locator)
  1411. self.isDefault_minloc = False
  1412. self.minor.locator = locator
  1413. if self.minor.formatter:
  1414. self.minor.formatter._set_locator(locator)
  1415. locator.set_axis(self)
  1416. self.stale = True
  1417. def set_pickradius(self, pickradius):
  1418. """
  1419. Set the depth of the axis used by the picker.
  1420. Parameters
  1421. ----------
  1422. pickradius : float
  1423. """
  1424. self.pickradius = pickradius
  1425. def set_ticklabels(self, ticklabels, *args, minor=False, **kwargs):
  1426. r"""
  1427. Set the text values of the tick labels.
  1428. Parameters
  1429. ----------
  1430. ticklabels : sequence of str or of `Text`\s
  1431. List of texts for tick labels; must include values for non-visible
  1432. labels.
  1433. minor : bool
  1434. If True, set minor ticks instead of major ticks.
  1435. **kwargs
  1436. Text properties.
  1437. Returns
  1438. -------
  1439. labels : list of `Text`\s
  1440. For each tick, includes ``tick.label1`` if it is visible, then
  1441. ``tick.label2`` if it is visible, in that order.
  1442. """
  1443. if args:
  1444. cbook.warn_deprecated(
  1445. "3.1", message="Additional positional arguments to "
  1446. "set_ticklabels are ignored, and deprecated since Matplotlib "
  1447. "3.1; passing them will raise a TypeError in Matplotlib 3.3.")
  1448. get_labels = []
  1449. for t in ticklabels:
  1450. # try calling get_text() to check whether it is Text object
  1451. # if it is Text, get label content
  1452. try:
  1453. get_labels.append(t.get_text())
  1454. # otherwise add the label to the list directly
  1455. except AttributeError:
  1456. get_labels.append(t)
  1457. # replace the ticklabels list with the processed one
  1458. ticklabels = get_labels
  1459. if minor:
  1460. self.set_minor_formatter(mticker.FixedFormatter(ticklabels))
  1461. ticks = self.get_minor_ticks()
  1462. else:
  1463. self.set_major_formatter(mticker.FixedFormatter(ticklabels))
  1464. ticks = self.get_major_ticks()
  1465. ret = []
  1466. for tick_label, tick in zip(ticklabels, ticks):
  1467. # deal with label1
  1468. tick.label1.set_text(tick_label)
  1469. tick.label1.update(kwargs)
  1470. # deal with label2
  1471. tick.label2.set_text(tick_label)
  1472. tick.label2.update(kwargs)
  1473. # only return visible tick labels
  1474. if tick.label1.get_visible():
  1475. ret.append(tick.label1)
  1476. if tick.label2.get_visible():
  1477. ret.append(tick.label2)
  1478. self.stale = True
  1479. return ret
  1480. @cbook._make_keyword_only("3.2", "minor")
  1481. def set_ticks(self, ticks, minor=False):
  1482. """
  1483. Set the locations of the tick marks from sequence ticks
  1484. Parameters
  1485. ----------
  1486. ticks : sequence of floats
  1487. minor : bool
  1488. """
  1489. # XXX if the user changes units, the information will be lost here
  1490. ticks = self.convert_units(ticks)
  1491. if len(ticks) > 1:
  1492. xleft, xright = self.get_view_interval()
  1493. if xright > xleft:
  1494. self.set_view_interval(min(ticks), max(ticks))
  1495. else:
  1496. self.set_view_interval(max(ticks), min(ticks))
  1497. if minor:
  1498. self.set_minor_locator(mticker.FixedLocator(ticks))
  1499. return self.get_minor_ticks(len(ticks))
  1500. else:
  1501. self.set_major_locator(mticker.FixedLocator(ticks))
  1502. return self.get_major_ticks(len(ticks))
  1503. def _get_tick_boxes_siblings(self, xdir, renderer):
  1504. """
  1505. Get the bounding boxes for this `.axis` and its siblings
  1506. as set by `.Figure.align_xlabels` or `.Figure.align_ylablels`.
  1507. By default it just gets bboxes for self.
  1508. """
  1509. raise NotImplementedError('Derived must override')
  1510. def _update_label_position(self, renderer):
  1511. """
  1512. Update the label position based on the bounding box enclosing
  1513. all the ticklabels and axis spine.
  1514. """
  1515. raise NotImplementedError('Derived must override')
  1516. def _update_offset_text_position(self, bboxes, bboxes2):
  1517. """
  1518. Update the offset text position based on the sequence of bounding
  1519. boxes of all the ticklabels.
  1520. """
  1521. raise NotImplementedError('Derived must override')
  1522. def pan(self, numsteps):
  1523. """Pan by *numsteps* (can be positive or negative)."""
  1524. self.major.locator.pan(numsteps)
  1525. def zoom(self, direction):
  1526. """Zoom in/out on axis; if *direction* is >0 zoom in, else zoom out."""
  1527. self.major.locator.zoom(direction)
  1528. def axis_date(self, tz=None):
  1529. """
  1530. Sets up axis ticks and labels treating data along this axis as dates.
  1531. Parameters
  1532. ----------
  1533. tz : tzinfo or str or None
  1534. The timezone used to create date labels.
  1535. """
  1536. # By providing a sample datetime instance with the desired timezone,
  1537. # the registered converter can be selected, and the "units" attribute,
  1538. # which is the timezone, can be set.
  1539. if isinstance(tz, str):
  1540. import dateutil.tz
  1541. tz = dateutil.tz.gettz(tz)
  1542. self.update_units(datetime.datetime(2009, 1, 1, 0, 0, 0, 0, tz))
  1543. def get_tick_space(self):
  1544. """Return the estimated number of ticks that can fit on the axis."""
  1545. # Must be overridden in the subclass
  1546. raise NotImplementedError()
  1547. def _get_ticks_position(self):
  1548. """
  1549. Helper for `XAxis.get_ticks_position` and `YAxis.get_ticks_position`.
  1550. Check the visibility of tick1line, label1, tick2line, and label2 on
  1551. the first major and the first minor ticks, and return
  1552. - 1 if only tick1line and label1 are visible (which corresponds to
  1553. "bottom" for the x-axis and "left" for the y-axis);
  1554. - 2 if only tick2line and label2 are visible (which corresponds to
  1555. "top" for the x-axis and "right" for the y-axis);
  1556. - "default" if only tick1line, tick2line and label1 are visible;
  1557. - "unknown" otherwise.
  1558. """
  1559. major = self.majorTicks[0]
  1560. minor = self.minorTicks[0]
  1561. if all(tick.tick1line.get_visible()
  1562. and not tick.tick2line.get_visible()
  1563. and tick.label1.get_visible()
  1564. and not tick.label2.get_visible()
  1565. for tick in [major, minor]):
  1566. return 1
  1567. elif all(tick.tick2line.get_visible()
  1568. and not tick.tick1line.get_visible()
  1569. and tick.label2.get_visible()
  1570. and not tick.label1.get_visible()
  1571. for tick in [major, minor]):
  1572. return 2
  1573. elif all(tick.tick1line.get_visible()
  1574. and tick.tick2line.get_visible()
  1575. and tick.label1.get_visible()
  1576. and not tick.label2.get_visible()
  1577. for tick in [major, minor]):
  1578. return "default"
  1579. else:
  1580. return "unknown"
  1581. def get_label_position(self):
  1582. """
  1583. Return the label position (top or bottom)
  1584. """
  1585. return self.label_position
  1586. def set_label_position(self, position):
  1587. """
  1588. Set the label position (top or bottom)
  1589. Parameters
  1590. ----------
  1591. position : {'top', 'bottom'}
  1592. """
  1593. raise NotImplementedError()
  1594. def get_minpos(self):
  1595. raise NotImplementedError()
  1596. def _make_getset_interval(method_name, lim_name, attr_name):
  1597. """
  1598. Helper to generate ``get_{data,view}_interval`` and
  1599. ``set_{data,view}_interval`` implementations.
  1600. """
  1601. def getter(self):
  1602. # docstring inherited.
  1603. return getattr(getattr(self.axes, lim_name), attr_name)
  1604. def setter(self, vmin, vmax, ignore=False):
  1605. # docstring inherited.
  1606. if ignore:
  1607. setattr(getattr(self.axes, lim_name), attr_name, (vmin, vmax))
  1608. else:
  1609. oldmin, oldmax = getter(self)
  1610. if oldmin < oldmax:
  1611. setter(self, min(vmin, vmax, oldmin), max(vmin, vmax, oldmax),
  1612. ignore=True)
  1613. else:
  1614. setter(self, max(vmin, vmax, oldmin), min(vmin, vmax, oldmax),
  1615. ignore=True)
  1616. self.stale = True
  1617. getter.__name__ = f"get_{method_name}_interval"
  1618. setter.__name__ = f"set_{method_name}_interval"
  1619. return getter, setter
  1620. class XAxis(Axis):
  1621. __name__ = 'xaxis'
  1622. axis_name = 'x' #: Read-only name identifying the axis.
  1623. def contains(self, mouseevent):
  1624. """Test whether the mouse event occurred in the x axis.
  1625. """
  1626. inside, info = self._default_contains(mouseevent)
  1627. if inside is not None:
  1628. return inside, info
  1629. x, y = mouseevent.x, mouseevent.y
  1630. try:
  1631. trans = self.axes.transAxes.inverted()
  1632. xaxes, yaxes = trans.transform((x, y))
  1633. except ValueError:
  1634. return False, {}
  1635. (l, b), (r, t) = self.axes.transAxes.transform([(0, 0), (1, 1)])
  1636. inaxis = 0 <= xaxes <= 1 and (
  1637. b - self.pickradius < y < b or
  1638. t < y < t + self.pickradius)
  1639. return inaxis, {}
  1640. def _get_tick(self, major):
  1641. if major:
  1642. tick_kw = self._major_tick_kw
  1643. else:
  1644. tick_kw = self._minor_tick_kw
  1645. return XTick(self.axes, 0, '', major=major, **tick_kw)
  1646. def _get_label(self):
  1647. # x in axes coords, y in display coords (to be updated at draw
  1648. # time by _update_label_positions)
  1649. label = mtext.Text(x=0.5, y=0,
  1650. fontproperties=font_manager.FontProperties(
  1651. size=rcParams['axes.labelsize'],
  1652. weight=rcParams['axes.labelweight']),
  1653. color=rcParams['axes.labelcolor'],
  1654. verticalalignment='top',
  1655. horizontalalignment='center')
  1656. label.set_transform(mtransforms.blended_transform_factory(
  1657. self.axes.transAxes, mtransforms.IdentityTransform()))
  1658. self._set_artist_props(label)
  1659. self.label_position = 'bottom'
  1660. return label
  1661. def _get_offset_text(self):
  1662. # x in axes coords, y in display coords (to be updated at draw time)
  1663. offsetText = mtext.Text(x=1, y=0,
  1664. fontproperties=font_manager.FontProperties(
  1665. size=rcParams['xtick.labelsize']),
  1666. color=rcParams['xtick.color'],
  1667. verticalalignment='top',
  1668. horizontalalignment='right')
  1669. offsetText.set_transform(mtransforms.blended_transform_factory(
  1670. self.axes.transAxes, mtransforms.IdentityTransform())
  1671. )
  1672. self._set_artist_props(offsetText)
  1673. self.offset_text_position = 'bottom'
  1674. return offsetText
  1675. def set_label_position(self, position):
  1676. """
  1677. Set the label position (top or bottom)
  1678. Parameters
  1679. ----------
  1680. position : {'top', 'bottom'}
  1681. """
  1682. self.label.set_verticalalignment(cbook._check_getitem({
  1683. 'top': 'baseline', 'bottom': 'top',
  1684. }, position=position))
  1685. self.label_position = position
  1686. self.stale = True
  1687. def _get_tick_boxes_siblings(self, renderer):
  1688. """
  1689. Get the bounding boxes for this `.axis` and its siblings
  1690. as set by `.Figure.align_xlabels` or `.Figure.align_ylablels`.
  1691. By default it just gets bboxes for self.
  1692. """
  1693. bboxes = []
  1694. bboxes2 = []
  1695. # get the Grouper that keeps track of x-label groups for this figure
  1696. grp = self.figure._align_xlabel_grp
  1697. # if we want to align labels from other axes:
  1698. for nn, axx in enumerate(grp.get_siblings(self.axes)):
  1699. ticks_to_draw = axx.xaxis._update_ticks()
  1700. tlb, tlb2 = axx.xaxis._get_tick_bboxes(ticks_to_draw, renderer)
  1701. bboxes.extend(tlb)
  1702. bboxes2.extend(tlb2)
  1703. return bboxes, bboxes2
  1704. def _update_label_position(self, renderer):
  1705. """
  1706. Update the label position based on the bounding box enclosing
  1707. all the ticklabels and axis spine
  1708. """
  1709. if not self._autolabelpos:
  1710. return
  1711. # get bounding boxes for this axis and any siblings
  1712. # that have been set by `fig.align_xlabels()`
  1713. bboxes, bboxes2 = self._get_tick_boxes_siblings(renderer=renderer)
  1714. x, y = self.label.get_position()
  1715. if self.label_position == 'bottom':
  1716. try:
  1717. spine = self.axes.spines['bottom']
  1718. spinebbox = spine.get_transform().transform_path(
  1719. spine.get_path()).get_extents()
  1720. except KeyError:
  1721. # use axes if spine doesn't exist
  1722. spinebbox = self.axes.bbox
  1723. bbox = mtransforms.Bbox.union(bboxes + [spinebbox])
  1724. bottom = bbox.y0
  1725. self.label.set_position(
  1726. (x, bottom - self.labelpad * self.figure.dpi / 72)
  1727. )
  1728. else:
  1729. try:
  1730. spine = self.axes.spines['top']
  1731. spinebbox = spine.get_transform().transform_path(
  1732. spine.get_path()).get_extents()
  1733. except KeyError:
  1734. # use axes if spine doesn't exist
  1735. spinebbox = self.axes.bbox
  1736. bbox = mtransforms.Bbox.union(bboxes2 + [spinebbox])
  1737. top = bbox.y1
  1738. self.label.set_position(
  1739. (x, top + self.labelpad * self.figure.dpi / 72)
  1740. )
  1741. def _update_offset_text_position(self, bboxes, bboxes2):
  1742. """
  1743. Update the offset_text position based on the sequence of bounding
  1744. boxes of all the ticklabels
  1745. """
  1746. x, y = self.offsetText.get_position()
  1747. if not len(bboxes):
  1748. bottom = self.axes.bbox.ymin
  1749. else:
  1750. bbox = mtransforms.Bbox.union(bboxes)
  1751. bottom = bbox.y0
  1752. self.offsetText.set_position(
  1753. (x, bottom - self.OFFSETTEXTPAD * self.figure.dpi / 72)
  1754. )
  1755. def get_text_heights(self, renderer):
  1756. """
  1757. Returns the amount of space one should reserve for text
  1758. above and below the axes. Returns a tuple (above, below)
  1759. """
  1760. bbox, bbox2 = self.get_ticklabel_extents(renderer)
  1761. # MGDTODO: Need a better way to get the pad
  1762. padPixels = self.majorTicks[0].get_pad_pixels()
  1763. above = 0.0
  1764. if bbox2.height:
  1765. above += bbox2.height + padPixels
  1766. below = 0.0
  1767. if bbox.height:
  1768. below += bbox.height + padPixels
  1769. if self.get_label_position() == 'top':
  1770. above += self.label.get_window_extent(renderer).height + padPixels
  1771. else:
  1772. below += self.label.get_window_extent(renderer).height + padPixels
  1773. return above, below
  1774. def set_ticks_position(self, position):
  1775. """
  1776. Set the ticks position (top, bottom, both, default or none)
  1777. both sets the ticks to appear on both positions, but does not
  1778. change the tick labels. 'default' resets the tick positions to
  1779. the default: ticks on both positions, labels at bottom. 'none'
  1780. can be used if you don't want any ticks. 'none' and 'both'
  1781. affect only the ticks, not the labels.
  1782. Parameters
  1783. ----------
  1784. position : {'top', 'bottom', 'both', 'default', 'none'}
  1785. """
  1786. if position == 'top':
  1787. self.set_tick_params(which='both', top=True, labeltop=True,
  1788. bottom=False, labelbottom=False)
  1789. elif position == 'bottom':
  1790. self.set_tick_params(which='both', top=False, labeltop=False,
  1791. bottom=True, labelbottom=True)
  1792. elif position == 'both':
  1793. self.set_tick_params(which='both', top=True,
  1794. bottom=True)
  1795. elif position == 'none':
  1796. self.set_tick_params(which='both', top=False,
  1797. bottom=False)
  1798. elif position == 'default':
  1799. self.set_tick_params(which='both', top=True, labeltop=False,
  1800. bottom=True, labelbottom=True)
  1801. else:
  1802. raise ValueError("invalid position: %s" % position)
  1803. self.stale = True
  1804. def tick_top(self):
  1805. """
  1806. Move ticks and ticklabels (if present) to the top of the axes.
  1807. """
  1808. label = True
  1809. if 'label1On' in self._major_tick_kw:
  1810. label = (self._major_tick_kw['label1On']
  1811. or self._major_tick_kw['label2On'])
  1812. self.set_ticks_position('top')
  1813. # If labels were turned off before this was called, leave them off.
  1814. self.set_tick_params(which='both', labeltop=label)
  1815. def tick_bottom(self):
  1816. """
  1817. Move ticks and ticklabels (if present) to the bottom of the axes.
  1818. """
  1819. label = True
  1820. if 'label1On' in self._major_tick_kw:
  1821. label = (self._major_tick_kw['label1On']
  1822. or self._major_tick_kw['label2On'])
  1823. self.set_ticks_position('bottom')
  1824. # If labels were turned off before this was called, leave them off.
  1825. self.set_tick_params(which='both', labelbottom=label)
  1826. def get_ticks_position(self):
  1827. """
  1828. Return the ticks position ("top", "bottom", "default", or "unknown").
  1829. """
  1830. return {1: "bottom", 2: "top",
  1831. "default": "default", "unknown": "unknown"}[
  1832. self._get_ticks_position()]
  1833. get_view_interval, set_view_interval = _make_getset_interval(
  1834. "view", "viewLim", "intervalx")
  1835. get_data_interval, set_data_interval = _make_getset_interval(
  1836. "data", "dataLim", "intervalx")
  1837. def get_minpos(self):
  1838. return self.axes.dataLim.minposx
  1839. def set_inverted(self, inverted):
  1840. # docstring inherited
  1841. a, b = self.get_view_interval()
  1842. # cast to bool to avoid bad interaction between python 3.8 and np.bool_
  1843. self.axes.set_xlim(sorted((a, b), reverse=bool(inverted)), auto=None)
  1844. def set_default_intervals(self):
  1845. # docstring inherited
  1846. xmin, xmax = 0., 1.
  1847. dataMutated = self.axes.dataLim.mutatedx()
  1848. viewMutated = self.axes.viewLim.mutatedx()
  1849. if not dataMutated or not viewMutated:
  1850. if self.converter is not None:
  1851. info = self.converter.axisinfo(self.units, self)
  1852. if info.default_limits is not None:
  1853. valmin, valmax = info.default_limits
  1854. xmin = self.converter.convert(valmin, self.units, self)
  1855. xmax = self.converter.convert(valmax, self.units, self)
  1856. if not dataMutated:
  1857. self.axes.dataLim.intervalx = xmin, xmax
  1858. if not viewMutated:
  1859. self.axes.viewLim.intervalx = xmin, xmax
  1860. self.stale = True
  1861. def get_tick_space(self):
  1862. ends = self.axes.transAxes.transform([[0, 0], [1, 0]])
  1863. length = ((ends[1][0] - ends[0][0]) / self.axes.figure.dpi) * 72
  1864. tick = self._get_tick(True)
  1865. # There is a heuristic here that the aspect ratio of tick text
  1866. # is no more than 3:1
  1867. size = tick.label1.get_size() * 3
  1868. if size > 0:
  1869. return int(np.floor(length / size))
  1870. else:
  1871. return 2**31 - 1
  1872. class YAxis(Axis):
  1873. __name__ = 'yaxis'
  1874. axis_name = 'y' #: Read-only name identifying the axis.
  1875. def contains(self, mouseevent):
  1876. """Test whether the mouse event occurred in the y axis.
  1877. Returns *True* | *False*
  1878. """
  1879. inside, info = self._default_contains(mouseevent)
  1880. if inside is not None:
  1881. return inside, info
  1882. x, y = mouseevent.x, mouseevent.y
  1883. try:
  1884. trans = self.axes.transAxes.inverted()
  1885. xaxes, yaxes = trans.transform((x, y))
  1886. except ValueError:
  1887. return False, {}
  1888. (l, b), (r, t) = self.axes.transAxes.transform([(0, 0), (1, 1)])
  1889. inaxis = 0 <= yaxes <= 1 and (
  1890. l - self.pickradius < x < l or
  1891. r < x < r + self.pickradius)
  1892. return inaxis, {}
  1893. def _get_tick(self, major):
  1894. if major:
  1895. tick_kw = self._major_tick_kw
  1896. else:
  1897. tick_kw = self._minor_tick_kw
  1898. return YTick(self.axes, 0, '', major=major, **tick_kw)
  1899. def _get_label(self):
  1900. # x in display coords (updated by _update_label_position)
  1901. # y in axes coords
  1902. label = mtext.Text(x=0, y=0.5,
  1903. # todo: get the label position
  1904. fontproperties=font_manager.FontProperties(
  1905. size=rcParams['axes.labelsize'],
  1906. weight=rcParams['axes.labelweight']),
  1907. color=rcParams['axes.labelcolor'],
  1908. verticalalignment='bottom',
  1909. horizontalalignment='center',
  1910. rotation='vertical',
  1911. rotation_mode='anchor')
  1912. label.set_transform(mtransforms.blended_transform_factory(
  1913. mtransforms.IdentityTransform(), self.axes.transAxes))
  1914. self._set_artist_props(label)
  1915. self.label_position = 'left'
  1916. return label
  1917. def _get_offset_text(self):
  1918. # x in display coords, y in axes coords (to be updated at draw time)
  1919. offsetText = mtext.Text(x=0, y=0.5,
  1920. fontproperties=font_manager.FontProperties(
  1921. size=rcParams['ytick.labelsize']
  1922. ),
  1923. color=rcParams['ytick.color'],
  1924. verticalalignment='baseline',
  1925. horizontalalignment='left')
  1926. offsetText.set_transform(mtransforms.blended_transform_factory(
  1927. self.axes.transAxes, mtransforms.IdentityTransform())
  1928. )
  1929. self._set_artist_props(offsetText)
  1930. self.offset_text_position = 'left'
  1931. return offsetText
  1932. def set_label_position(self, position):
  1933. """
  1934. Set the label position (left or right)
  1935. Parameters
  1936. ----------
  1937. position : {'left', 'right'}
  1938. """
  1939. self.label.set_rotation_mode('anchor')
  1940. self.label.set_horizontalalignment('center')
  1941. self.label.set_verticalalignment(cbook._check_getitem({
  1942. 'left': 'bottom', 'right': 'top',
  1943. }, position=position))
  1944. self.label_position = position
  1945. self.stale = True
  1946. def _get_tick_boxes_siblings(self, renderer):
  1947. """
  1948. Get the bounding boxes for this `.axis` and its siblings
  1949. as set by `.Figure.align_xlabels` or `.Figure.align_ylablels`.
  1950. By default it just gets bboxes for self.
  1951. """
  1952. bboxes = []
  1953. bboxes2 = []
  1954. # get the Grouper that keeps track of y-label groups for this figure
  1955. grp = self.figure._align_ylabel_grp
  1956. # if we want to align labels from other axes:
  1957. for axx in grp.get_siblings(self.axes):
  1958. ticks_to_draw = axx.yaxis._update_ticks()
  1959. tlb, tlb2 = axx.yaxis._get_tick_bboxes(ticks_to_draw, renderer)
  1960. bboxes.extend(tlb)
  1961. bboxes2.extend(tlb2)
  1962. return bboxes, bboxes2
  1963. def _update_label_position(self, renderer):
  1964. """
  1965. Update the label position based on the bounding box enclosing
  1966. all the ticklabels and axis spine
  1967. """
  1968. if not self._autolabelpos:
  1969. return
  1970. # get bounding boxes for this axis and any siblings
  1971. # that have been set by `fig.align_ylabels()`
  1972. bboxes, bboxes2 = self._get_tick_boxes_siblings(renderer=renderer)
  1973. x, y = self.label.get_position()
  1974. if self.label_position == 'left':
  1975. try:
  1976. spine = self.axes.spines['left']
  1977. spinebbox = spine.get_transform().transform_path(
  1978. spine.get_path()).get_extents()
  1979. except KeyError:
  1980. # use axes if spine doesn't exist
  1981. spinebbox = self.axes.bbox
  1982. bbox = mtransforms.Bbox.union(bboxes + [spinebbox])
  1983. left = bbox.x0
  1984. self.label.set_position(
  1985. (left - self.labelpad * self.figure.dpi / 72, y)
  1986. )
  1987. else:
  1988. try:
  1989. spine = self.axes.spines['right']
  1990. spinebbox = spine.get_transform().transform_path(
  1991. spine.get_path()).get_extents()
  1992. except KeyError:
  1993. # use axes if spine doesn't exist
  1994. spinebbox = self.axes.bbox
  1995. bbox = mtransforms.Bbox.union(bboxes2 + [spinebbox])
  1996. right = bbox.x1
  1997. self.label.set_position(
  1998. (right + self.labelpad * self.figure.dpi / 72, y)
  1999. )
  2000. def _update_offset_text_position(self, bboxes, bboxes2):
  2001. """
  2002. Update the offset_text position based on the sequence of bounding
  2003. boxes of all the ticklabels
  2004. """
  2005. x, y = self.offsetText.get_position()
  2006. top = self.axes.bbox.ymax
  2007. self.offsetText.set_position(
  2008. (x, top + self.OFFSETTEXTPAD * self.figure.dpi / 72)
  2009. )
  2010. def set_offset_position(self, position):
  2011. """
  2012. Parameters
  2013. ----------
  2014. position : {'left', 'right'}
  2015. """
  2016. x, y = self.offsetText.get_position()
  2017. x = cbook._check_getitem({'left': 0, 'right': 1}, position=position)
  2018. self.offsetText.set_ha(position)
  2019. self.offsetText.set_position((x, y))
  2020. self.stale = True
  2021. def get_text_widths(self, renderer):
  2022. bbox, bbox2 = self.get_ticklabel_extents(renderer)
  2023. # MGDTODO: Need a better way to get the pad
  2024. padPixels = self.majorTicks[0].get_pad_pixels()
  2025. left = 0.0
  2026. if bbox.width:
  2027. left += bbox.width + padPixels
  2028. right = 0.0
  2029. if bbox2.width:
  2030. right += bbox2.width + padPixels
  2031. if self.get_label_position() == 'left':
  2032. left += self.label.get_window_extent(renderer).width + padPixels
  2033. else:
  2034. right += self.label.get_window_extent(renderer).width + padPixels
  2035. return left, right
  2036. def set_ticks_position(self, position):
  2037. """
  2038. Set the ticks position (left, right, both, default or none)
  2039. 'both' sets the ticks to appear on both positions, but does not
  2040. change the tick labels. 'default' resets the tick positions to
  2041. the default: ticks on both positions, labels at left. 'none'
  2042. can be used if you don't want any ticks. 'none' and 'both'
  2043. affect only the ticks, not the labels.
  2044. Parameters
  2045. ----------
  2046. position : {'left', 'right', 'both', 'default', 'none'}
  2047. """
  2048. if position == 'right':
  2049. self.set_tick_params(which='both', right=True, labelright=True,
  2050. left=False, labelleft=False)
  2051. self.set_offset_position(position)
  2052. elif position == 'left':
  2053. self.set_tick_params(which='both', right=False, labelright=False,
  2054. left=True, labelleft=True)
  2055. self.set_offset_position(position)
  2056. elif position == 'both':
  2057. self.set_tick_params(which='both', right=True,
  2058. left=True)
  2059. elif position == 'none':
  2060. self.set_tick_params(which='both', right=False,
  2061. left=False)
  2062. elif position == 'default':
  2063. self.set_tick_params(which='both', right=True, labelright=False,
  2064. left=True, labelleft=True)
  2065. else:
  2066. raise ValueError("invalid position: %s" % position)
  2067. self.stale = True
  2068. def tick_right(self):
  2069. """
  2070. Move ticks and ticklabels (if present) to the right of the axes.
  2071. """
  2072. label = True
  2073. if 'label1On' in self._major_tick_kw:
  2074. label = (self._major_tick_kw['label1On']
  2075. or self._major_tick_kw['label2On'])
  2076. self.set_ticks_position('right')
  2077. # if labels were turned off before this was called
  2078. # leave them off
  2079. self.set_tick_params(which='both', labelright=label)
  2080. def tick_left(self):
  2081. """
  2082. Move ticks and ticklabels (if present) to the left of the axes.
  2083. """
  2084. label = True
  2085. if 'label1On' in self._major_tick_kw:
  2086. label = (self._major_tick_kw['label1On']
  2087. or self._major_tick_kw['label2On'])
  2088. self.set_ticks_position('left')
  2089. # if labels were turned off before this was called
  2090. # leave them off
  2091. self.set_tick_params(which='both', labelleft=label)
  2092. def get_ticks_position(self):
  2093. """
  2094. Return the ticks position ("left", "right", "default", or "unknown").
  2095. """
  2096. return {1: "left", 2: "right",
  2097. "default": "default", "unknown": "unknown"}[
  2098. self._get_ticks_position()]
  2099. get_view_interval, set_view_interval = _make_getset_interval(
  2100. "view", "viewLim", "intervaly")
  2101. get_data_interval, set_data_interval = _make_getset_interval(
  2102. "data", "dataLim", "intervaly")
  2103. def get_minpos(self):
  2104. return self.axes.dataLim.minposy
  2105. def set_inverted(self, inverted):
  2106. # docstring inherited
  2107. a, b = self.get_view_interval()
  2108. # cast to bool to avoid bad interaction between python 3.8 and np.bool_
  2109. self.axes.set_ylim(sorted((a, b), reverse=bool(inverted)), auto=None)
  2110. def set_default_intervals(self):
  2111. # docstring inherited
  2112. ymin, ymax = 0., 1.
  2113. dataMutated = self.axes.dataLim.mutatedy()
  2114. viewMutated = self.axes.viewLim.mutatedy()
  2115. if not dataMutated or not viewMutated:
  2116. if self.converter is not None:
  2117. info = self.converter.axisinfo(self.units, self)
  2118. if info.default_limits is not None:
  2119. valmin, valmax = info.default_limits
  2120. ymin = self.converter.convert(valmin, self.units, self)
  2121. ymax = self.converter.convert(valmax, self.units, self)
  2122. if not dataMutated:
  2123. self.axes.dataLim.intervaly = ymin, ymax
  2124. if not viewMutated:
  2125. self.axes.viewLim.intervaly = ymin, ymax
  2126. self.stale = True
  2127. def get_tick_space(self):
  2128. ends = self.axes.transAxes.transform([[0, 0], [0, 1]])
  2129. length = ((ends[1][1] - ends[0][1]) / self.axes.figure.dpi) * 72
  2130. tick = self._get_tick(True)
  2131. # Having a spacing of at least 2 just looks good.
  2132. size = tick.label1.get_size() * 2.0
  2133. if size > 0:
  2134. return int(np.floor(length / size))
  2135. else:
  2136. return 2**31 - 1