You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

2545 lines
89 KiB

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