axes_divider.py 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975
  1. """
  2. The axes_divider module provides helper classes to adjust the positions of
  3. multiple axes at drawing time.
  4. Divider: this is the class that is used to calculate the axes
  5. position. It divides the given rectangular area into several sub
  6. rectangles. You initialize the divider by setting the horizontal
  7. and vertical lists of sizes that the division will be based on. You
  8. then use the new_locator method, whose return value is a callable
  9. object that can be used to set the axes_locator of the axes.
  10. """
  11. from __future__ import (absolute_import, division, print_function,
  12. unicode_literals)
  13. import six
  14. from six.moves import map
  15. import matplotlib.transforms as mtransforms
  16. from matplotlib.axes import SubplotBase
  17. from . import axes_size as Size
  18. class Divider(object):
  19. """
  20. This class calculates the axes position. It
  21. divides the given rectangular area into several
  22. sub-rectangles. You initialize the divider by setting the
  23. horizontal and vertical lists of sizes
  24. (:mod:`mpl_toolkits.axes_grid.axes_size`) that the division will
  25. be based on. You then use the new_locator method to create a
  26. callable object that can be used as the axes_locator of the
  27. axes.
  28. """
  29. def __init__(self, fig, pos, horizontal, vertical,
  30. aspect=None, anchor="C"):
  31. """
  32. Parameters
  33. ----------
  34. fig : Figure
  35. pos : tuple of 4 floats
  36. position of the rectangle that will be divided
  37. horizontal : list of :mod:`~mpl_toolkits.axes_grid.axes_size`
  38. sizes for horizontal division
  39. vertical : list of :mod:`~mpl_toolkits.axes_grid.axes_size`
  40. sizes for vertical division
  41. aspect : bool
  42. if True, the overall rectangular area is reduced
  43. so that the relative part of the horizontal and
  44. vertical scales have the same scale.
  45. anchor : {'C', 'SW', 'S', 'SE', 'E', 'NE', 'N', 'NW', 'W'}
  46. placement of the reduced rectangle when *aspect* is True
  47. """
  48. self._fig = fig
  49. self._pos = pos
  50. self._horizontal = horizontal
  51. self._vertical = vertical
  52. self._anchor = anchor
  53. self._aspect = aspect
  54. self._xrefindex = 0
  55. self._yrefindex = 0
  56. self._locator = None
  57. def get_horizontal_sizes(self, renderer):
  58. return [s.get_size(renderer) for s in self.get_horizontal()]
  59. def get_vertical_sizes(self, renderer):
  60. return [s.get_size(renderer) for s in self.get_vertical()]
  61. def get_vsize_hsize(self):
  62. from .axes_size import AddList
  63. vsize = AddList(self.get_vertical())
  64. hsize = AddList(self.get_horizontal())
  65. return vsize, hsize
  66. @staticmethod
  67. def _calc_k(l, total_size):
  68. rs_sum, as_sum = 0., 0.
  69. for _rs, _as in l:
  70. rs_sum += _rs
  71. as_sum += _as
  72. if rs_sum != 0.:
  73. k = (total_size - as_sum) / rs_sum
  74. return k
  75. else:
  76. return 0.
  77. @staticmethod
  78. def _calc_offsets(l, k):
  79. offsets = [0.]
  80. #for s in l:
  81. for _rs, _as in l:
  82. #_rs, _as = s.get_size(renderer)
  83. offsets.append(offsets[-1] + _rs*k + _as)
  84. return offsets
  85. def set_position(self, pos):
  86. """
  87. set the position of the rectangle.
  88. Parameters
  89. ----------
  90. pos : tuple of 4 floats
  91. position of the rectangle that will be divided
  92. """
  93. self._pos = pos
  94. def get_position(self):
  95. "return the position of the rectangle."
  96. return self._pos
  97. def set_anchor(self, anchor):
  98. """
  99. Parameters
  100. ----------
  101. anchor : {'C', 'SW', 'S', 'SE', 'E', 'NE', 'N', 'NW', 'W'}
  102. anchor position
  103. ===== ============
  104. value description
  105. ===== ============
  106. 'C' Center
  107. 'SW' bottom left
  108. 'S' bottom
  109. 'SE' bottom right
  110. 'E' right
  111. 'NE' top right
  112. 'N' top
  113. 'NW' top left
  114. 'W' left
  115. ===== ============
  116. """
  117. if anchor in mtransforms.Bbox.coefs or len(anchor) == 2:
  118. self._anchor = anchor
  119. else:
  120. raise ValueError('argument must be among %s' %
  121. ', '.join(mtransforms.BBox.coefs))
  122. def get_anchor(self):
  123. "return the anchor"
  124. return self._anchor
  125. def set_horizontal(self, h):
  126. """
  127. Parameters
  128. ----------
  129. h : list of :mod:`~mpl_toolkits.axes_grid.axes_size`
  130. sizes for horizontal division
  131. """
  132. self._horizontal = h
  133. def get_horizontal(self):
  134. "return horizontal sizes"
  135. return self._horizontal
  136. def set_vertical(self, v):
  137. """
  138. Parameters
  139. ----------
  140. v : list of :mod:`~mpl_toolkits.axes_grid.axes_size`
  141. sizes for vertical division
  142. """
  143. self._vertical = v
  144. def get_vertical(self):
  145. "return vertical sizes"
  146. return self._vertical
  147. def set_aspect(self, aspect=False):
  148. """
  149. Parameters
  150. ----------
  151. aspect : bool
  152. """
  153. self._aspect = aspect
  154. def get_aspect(self):
  155. "return aspect"
  156. return self._aspect
  157. def set_locator(self, _locator):
  158. self._locator = _locator
  159. def get_locator(self):
  160. return self._locator
  161. def get_position_runtime(self, ax, renderer):
  162. if self._locator is None:
  163. return self.get_position()
  164. else:
  165. return self._locator(ax, renderer).bounds
  166. def locate(self, nx, ny, nx1=None, ny1=None, axes=None, renderer=None):
  167. """
  168. Parameters
  169. ----------
  170. nx, nx1 : int
  171. Integers specifying the column-position of the
  172. cell. When *nx1* is None, a single *nx*-th column is
  173. specified. Otherwise location of columns spanning between *nx*
  174. to *nx1* (but excluding *nx1*-th column) is specified.
  175. ny, ny1 : int
  176. Same as *nx* and *nx1*, but for row positions.
  177. axes
  178. renderer
  179. """
  180. figW, figH = self._fig.get_size_inches()
  181. x, y, w, h = self.get_position_runtime(axes, renderer)
  182. hsizes = self.get_horizontal_sizes(renderer)
  183. vsizes = self.get_vertical_sizes(renderer)
  184. k_h = self._calc_k(hsizes, figW*w)
  185. k_v = self._calc_k(vsizes, figH*h)
  186. if self.get_aspect():
  187. k = min(k_h, k_v)
  188. ox = self._calc_offsets(hsizes, k)
  189. oy = self._calc_offsets(vsizes, k)
  190. ww = (ox[-1] - ox[0])/figW
  191. hh = (oy[-1] - oy[0])/figH
  192. pb = mtransforms.Bbox.from_bounds(x, y, w, h)
  193. pb1 = mtransforms.Bbox.from_bounds(x, y, ww, hh)
  194. pb1_anchored = pb1.anchored(self.get_anchor(), pb)
  195. x0, y0 = pb1_anchored.x0, pb1_anchored.y0
  196. else:
  197. ox = self._calc_offsets(hsizes, k_h)
  198. oy = self._calc_offsets(vsizes, k_v)
  199. x0, y0 = x, y
  200. if nx1 is None:
  201. nx1 = nx+1
  202. if ny1 is None:
  203. ny1 = ny+1
  204. x1, w1 = x0 + ox[nx]/figW, (ox[nx1] - ox[nx])/figW
  205. y1, h1 = y0 + oy[ny]/figH, (oy[ny1] - oy[ny])/figH
  206. return mtransforms.Bbox.from_bounds(x1, y1, w1, h1)
  207. def new_locator(self, nx, ny, nx1=None, ny1=None):
  208. """
  209. Returns a new locator
  210. (:class:`mpl_toolkits.axes_grid.axes_divider.AxesLocator`) for
  211. specified cell.
  212. Parameters
  213. ----------
  214. nx, nx1 : int
  215. Integers specifying the column-position of the
  216. cell. When *nx1* is None, a single *nx*-th column is
  217. specified. Otherwise location of columns spanning between *nx*
  218. to *nx1* (but excluding *nx1*-th column) is specified.
  219. ny, ny1 : int
  220. Same as *nx* and *nx1*, but for row positions.
  221. """
  222. return AxesLocator(self, nx, ny, nx1, ny1)
  223. def append_size(self, position, size):
  224. if position == "left":
  225. self._horizontal.insert(0, size)
  226. self._xrefindex += 1
  227. elif position == "right":
  228. self._horizontal.append(size)
  229. elif position == "bottom":
  230. self._vertical.insert(0, size)
  231. self._yrefindex += 1
  232. elif position == "top":
  233. self._vertical.append(size)
  234. else:
  235. raise ValueError("the position must be one of left," +
  236. " right, bottom, or top")
  237. def add_auto_adjustable_area(self,
  238. use_axes, pad=0.1,
  239. adjust_dirs=None,
  240. ):
  241. if adjust_dirs is None:
  242. adjust_dirs = ["left", "right", "bottom", "top"]
  243. from .axes_size import Padded, SizeFromFunc, GetExtentHelper
  244. for d in adjust_dirs:
  245. helper = GetExtentHelper(use_axes, d)
  246. size = SizeFromFunc(helper)
  247. padded_size = Padded(size, pad) # pad in inch
  248. self.append_size(d, padded_size)
  249. class AxesLocator(object):
  250. """
  251. A simple callable object, initialized with AxesDivider class,
  252. returns the position and size of the given cell.
  253. """
  254. def __init__(self, axes_divider, nx, ny, nx1=None, ny1=None):
  255. """
  256. Parameters
  257. ----------
  258. axes_divider : AxesDivider
  259. nx, nx1 : int
  260. Integers specifying the column-position of the
  261. cell. When *nx1* is None, a single *nx*-th column is
  262. specified. Otherwise location of columns spanning between *nx*
  263. to *nx1* (but excluding *nx1*-th column) is specified.
  264. ny, ny1 : int
  265. Same as *nx* and *nx1*, but for row positions.
  266. """
  267. self._axes_divider = axes_divider
  268. _xrefindex = axes_divider._xrefindex
  269. _yrefindex = axes_divider._yrefindex
  270. self._nx, self._ny = nx - _xrefindex, ny - _yrefindex
  271. if nx1 is None:
  272. nx1 = nx+1
  273. if ny1 is None:
  274. ny1 = ny+1
  275. self._nx1 = nx1 - _xrefindex
  276. self._ny1 = ny1 - _yrefindex
  277. def __call__(self, axes, renderer):
  278. _xrefindex = self._axes_divider._xrefindex
  279. _yrefindex = self._axes_divider._yrefindex
  280. return self._axes_divider.locate(self._nx + _xrefindex,
  281. self._ny + _yrefindex,
  282. self._nx1 + _xrefindex,
  283. self._ny1 + _yrefindex,
  284. axes,
  285. renderer)
  286. def get_subplotspec(self):
  287. if hasattr(self._axes_divider, "get_subplotspec"):
  288. return self._axes_divider.get_subplotspec()
  289. else:
  290. return None
  291. from matplotlib.gridspec import SubplotSpec, GridSpec
  292. class SubplotDivider(Divider):
  293. """
  294. The Divider class whose rectangle area is specified as a subplot geometry.
  295. """
  296. def __init__(self, fig, *args, **kwargs):
  297. """
  298. Parameters
  299. ----------
  300. fig : :class:`matplotlib.figure.Figure`
  301. args : tuple (*numRows*, *numCols*, *plotNum*)
  302. The array of subplots in the figure has dimensions *numRows*,
  303. *numCols*, and *plotNum* is the number of the subplot
  304. being created. *plotNum* starts at 1 in the upper left
  305. corner and increases to the right.
  306. If *numRows* <= *numCols* <= *plotNum* < 10, *args* can be the
  307. decimal integer *numRows* * 100 + *numCols* * 10 + *plotNum*.
  308. """
  309. self.figure = fig
  310. if len(args) == 1:
  311. if isinstance(args[0], SubplotSpec):
  312. self._subplotspec = args[0]
  313. else:
  314. try:
  315. s = str(int(args[0]))
  316. rows, cols, num = map(int, s)
  317. except ValueError:
  318. raise ValueError(
  319. 'Single argument to subplot must be a 3-digit integer')
  320. self._subplotspec = GridSpec(rows, cols)[num-1]
  321. # num - 1 for converting from MATLAB to python indexing
  322. elif len(args) == 3:
  323. rows, cols, num = args
  324. rows = int(rows)
  325. cols = int(cols)
  326. if isinstance(num, tuple) and len(num) == 2:
  327. num = [int(n) for n in num]
  328. self._subplotspec = GridSpec(rows, cols)[num[0]-1:num[1]]
  329. else:
  330. self._subplotspec = GridSpec(rows, cols)[int(num)-1]
  331. # num - 1 for converting from MATLAB to python indexing
  332. else:
  333. raise ValueError('Illegal argument(s) to subplot: %s' % (args,))
  334. # total = rows*cols
  335. # num -= 1 # convert from matlab to python indexing
  336. # # i.e., num in range(0,total)
  337. # if num >= total:
  338. # raise ValueError( 'Subplot number exceeds total subplots')
  339. # self._rows = rows
  340. # self._cols = cols
  341. # self._num = num
  342. # self.update_params()
  343. # sets self.fixbox
  344. self.update_params()
  345. pos = self.figbox.bounds
  346. horizontal = kwargs.pop("horizontal", [])
  347. vertical = kwargs.pop("vertical", [])
  348. aspect = kwargs.pop("aspect", None)
  349. anchor = kwargs.pop("anchor", "C")
  350. if kwargs:
  351. raise Exception("")
  352. Divider.__init__(self, fig, pos, horizontal, vertical,
  353. aspect=aspect, anchor=anchor)
  354. def get_position(self):
  355. "return the bounds of the subplot box"
  356. self.update_params() # update self.figbox
  357. return self.figbox.bounds
  358. # def update_params(self):
  359. # 'update the subplot position from fig.subplotpars'
  360. # rows = self._rows
  361. # cols = self._cols
  362. # num = self._num
  363. # pars = self.figure.subplotpars
  364. # left = pars.left
  365. # right = pars.right
  366. # bottom = pars.bottom
  367. # top = pars.top
  368. # wspace = pars.wspace
  369. # hspace = pars.hspace
  370. # totWidth = right-left
  371. # totHeight = top-bottom
  372. # figH = totHeight/(rows + hspace*(rows-1))
  373. # sepH = hspace*figH
  374. # figW = totWidth/(cols + wspace*(cols-1))
  375. # sepW = wspace*figW
  376. # rowNum, colNum = divmod(num, cols)
  377. # figBottom = top - (rowNum+1)*figH - rowNum*sepH
  378. # figLeft = left + colNum*(figW + sepW)
  379. # self.figbox = mtransforms.Bbox.from_bounds(figLeft, figBottom,
  380. # figW, figH)
  381. def update_params(self):
  382. 'update the subplot position from fig.subplotpars'
  383. self.figbox = self.get_subplotspec().get_position(self.figure)
  384. def get_geometry(self):
  385. 'get the subplot geometry, e.g., 2,2,3'
  386. rows, cols, num1, num2 = self.get_subplotspec().get_geometry()
  387. return rows, cols, num1+1 # for compatibility
  388. # COVERAGE NOTE: Never used internally or from examples
  389. def change_geometry(self, numrows, numcols, num):
  390. 'change subplot geometry, e.g., from 1,1,1 to 2,2,3'
  391. self._subplotspec = GridSpec(numrows, numcols)[num-1]
  392. self.update_params()
  393. self.set_position(self.figbox)
  394. def get_subplotspec(self):
  395. 'get the SubplotSpec instance'
  396. return self._subplotspec
  397. def set_subplotspec(self, subplotspec):
  398. 'set the SubplotSpec instance'
  399. self._subplotspec = subplotspec
  400. class AxesDivider(Divider):
  401. """
  402. Divider based on the pre-existing axes.
  403. """
  404. def __init__(self, axes, xref=None, yref=None):
  405. """
  406. Parameters
  407. ----------
  408. axes : :class:`~matplotlib.axes.Axes`
  409. xref
  410. yref
  411. """
  412. self._axes = axes
  413. if xref is None:
  414. self._xref = Size.AxesX(axes)
  415. else:
  416. self._xref = xref
  417. if yref is None:
  418. self._yref = Size.AxesY(axes)
  419. else:
  420. self._yref = yref
  421. Divider.__init__(self, fig=axes.get_figure(), pos=None,
  422. horizontal=[self._xref], vertical=[self._yref],
  423. aspect=None, anchor="C")
  424. def _get_new_axes(self, **kwargs):
  425. axes = self._axes
  426. axes_class = kwargs.pop("axes_class", None)
  427. if axes_class is None:
  428. if isinstance(axes, SubplotBase):
  429. axes_class = axes._axes_class
  430. else:
  431. axes_class = type(axes)
  432. ax = axes_class(axes.get_figure(),
  433. axes.get_position(original=True), **kwargs)
  434. return ax
  435. def new_horizontal(self, size, pad=None, pack_start=False, **kwargs):
  436. """
  437. Add a new axes on the right (or left) side of the main axes.
  438. Parameters
  439. ----------
  440. size : :mod:`~mpl_toolkits.axes_grid.axes_size` or float or string
  441. A width of the axes. If float or string is given, *from_any*
  442. function is used to create the size, with *ref_size* set to AxesX
  443. instance of the current axes.
  444. pad : :mod:`~mpl_toolkits.axes_grid.axes_size` or float or string
  445. Pad between the axes. It takes same argument as *size*.
  446. pack_start : bool
  447. If False, the new axes is appended at the end
  448. of the list, i.e., it became the right-most axes. If True, it is
  449. inserted at the start of the list, and becomes the left-most axes.
  450. kwargs
  451. All extra keywords arguments are passed to the created axes.
  452. If *axes_class* is given, the new axes will be created as an
  453. instance of the given class. Otherwise, the same class of the
  454. main axes will be used.
  455. """
  456. if pad:
  457. if not isinstance(pad, Size._Base):
  458. pad = Size.from_any(pad,
  459. fraction_ref=self._xref)
  460. if pack_start:
  461. self._horizontal.insert(0, pad)
  462. self._xrefindex += 1
  463. else:
  464. self._horizontal.append(pad)
  465. if not isinstance(size, Size._Base):
  466. size = Size.from_any(size,
  467. fraction_ref=self._xref)
  468. if pack_start:
  469. self._horizontal.insert(0, size)
  470. self._xrefindex += 1
  471. locator = self.new_locator(nx=0, ny=self._yrefindex)
  472. else:
  473. self._horizontal.append(size)
  474. locator = self.new_locator(nx=len(self._horizontal)-1, ny=self._yrefindex)
  475. ax = self._get_new_axes(**kwargs)
  476. ax.set_axes_locator(locator)
  477. return ax
  478. def new_vertical(self, size, pad=None, pack_start=False, **kwargs):
  479. """
  480. Add a new axes on the top (or bottom) side of the main axes.
  481. Parameters
  482. ----------
  483. size : :mod:`~mpl_toolkits.axes_grid.axes_size` or float or string
  484. A height of the axes. If float or string is given, *from_any*
  485. function is used to create the size, with *ref_size* set to AxesX
  486. instance of the current axes.
  487. pad : :mod:`~mpl_toolkits.axes_grid.axes_size` or float or string
  488. Pad between the axes. It takes same argument as *size*.
  489. pack_start : bool
  490. If False, the new axes is appended at the end
  491. of the list, i.e., it became the right-most axes. If True, it is
  492. inserted at the start of the list, and becomes the left-most axes.
  493. kwargs
  494. All extra keywords arguments are passed to the created axes.
  495. If *axes_class* is given, the new axes will be created as an
  496. instance of the given class. Otherwise, the same class of the
  497. main axes will be used.
  498. """
  499. if pad:
  500. if not isinstance(pad, Size._Base):
  501. pad = Size.from_any(pad,
  502. fraction_ref=self._yref)
  503. if pack_start:
  504. self._vertical.insert(0, pad)
  505. self._yrefindex += 1
  506. else:
  507. self._vertical.append(pad)
  508. if not isinstance(size, Size._Base):
  509. size = Size.from_any(size,
  510. fraction_ref=self._yref)
  511. if pack_start:
  512. self._vertical.insert(0, size)
  513. self._yrefindex += 1
  514. locator = self.new_locator(nx=self._xrefindex, ny=0)
  515. else:
  516. self._vertical.append(size)
  517. locator = self.new_locator(nx=self._xrefindex, ny=len(self._vertical)-1)
  518. ax = self._get_new_axes(**kwargs)
  519. ax.set_axes_locator(locator)
  520. return ax
  521. def append_axes(self, position, size, pad=None, add_to_figure=True,
  522. **kwargs):
  523. """
  524. create an axes at the given *position* with the same height
  525. (or width) of the main axes.
  526. *position*
  527. ["left"|"right"|"bottom"|"top"]
  528. *size* and *pad* should be axes_grid.axes_size compatible.
  529. """
  530. if position == "left":
  531. ax = self.new_horizontal(size, pad, pack_start=True, **kwargs)
  532. elif position == "right":
  533. ax = self.new_horizontal(size, pad, pack_start=False, **kwargs)
  534. elif position == "bottom":
  535. ax = self.new_vertical(size, pad, pack_start=True, **kwargs)
  536. elif position == "top":
  537. ax = self.new_vertical(size, pad, pack_start=False, **kwargs)
  538. else:
  539. raise ValueError("the position must be one of left," +
  540. " right, bottom, or top")
  541. if add_to_figure:
  542. self._fig.add_axes(ax)
  543. return ax
  544. def get_aspect(self):
  545. if self._aspect is None:
  546. aspect = self._axes.get_aspect()
  547. if aspect == "auto":
  548. return False
  549. else:
  550. return True
  551. else:
  552. return self._aspect
  553. def get_position(self):
  554. if self._pos is None:
  555. bbox = self._axes.get_position(original=True)
  556. return bbox.bounds
  557. else:
  558. return self._pos
  559. def get_anchor(self):
  560. if self._anchor is None:
  561. return self._axes.get_anchor()
  562. else:
  563. return self._anchor
  564. def get_subplotspec(self):
  565. if hasattr(self._axes, "get_subplotspec"):
  566. return self._axes.get_subplotspec()
  567. else:
  568. return None
  569. class HBoxDivider(SubplotDivider):
  570. def __init__(self, fig, *args, **kwargs):
  571. SubplotDivider.__init__(self, fig, *args, **kwargs)
  572. @staticmethod
  573. def _determine_karray(equivalent_sizes, appended_sizes,
  574. max_equivalent_size,
  575. total_appended_size):
  576. n = len(equivalent_sizes)
  577. import numpy as np
  578. A = np.mat(np.zeros((n+1, n+1), dtype="d"))
  579. B = np.zeros((n+1), dtype="d")
  580. # AxK = B
  581. # populated A
  582. for i, (r, a) in enumerate(equivalent_sizes):
  583. A[i, i] = r
  584. A[i, -1] = -1
  585. B[i] = -a
  586. A[-1, :-1] = [r for r, a in appended_sizes]
  587. B[-1] = total_appended_size - sum([a for rs, a in appended_sizes])
  588. karray_H = (A.I*np.mat(B).T).A1
  589. karray = karray_H[:-1]
  590. H = karray_H[-1]
  591. if H > max_equivalent_size:
  592. karray = ((max_equivalent_size -
  593. np.array([a for r, a in equivalent_sizes]))
  594. / np.array([r for r, a in equivalent_sizes]))
  595. return karray
  596. @staticmethod
  597. def _calc_offsets(appended_sizes, karray):
  598. offsets = [0.]
  599. #for s in l:
  600. for (r, a), k in zip(appended_sizes, karray):
  601. offsets.append(offsets[-1] + r*k + a)
  602. return offsets
  603. def new_locator(self, nx, nx1=None):
  604. """
  605. returns a new locator
  606. (:class:`mpl_toolkits.axes_grid.axes_divider.AxesLocator`) for
  607. specified cell.
  608. Parameters
  609. ----------
  610. nx, nx1 : int
  611. Integers specifying the column-position of the
  612. cell. When *nx1* is None, a single *nx*-th column is
  613. specified. Otherwise location of columns spanning between *nx*
  614. to *nx1* (but excluding *nx1*-th column) is specified.
  615. ny, ny1 : int
  616. Same as *nx* and *nx1*, but for row positions.
  617. """
  618. return AxesLocator(self, nx, 0, nx1, None)
  619. def _locate(self, x, y, w, h,
  620. y_equivalent_sizes, x_appended_sizes,
  621. figW, figH):
  622. """
  623. Parameters
  624. ----------
  625. x
  626. y
  627. w
  628. h
  629. y_equivalent_sizes
  630. x_appended_sizes
  631. figW
  632. figH
  633. """
  634. equivalent_sizes = y_equivalent_sizes
  635. appended_sizes = x_appended_sizes
  636. max_equivalent_size = figH*h
  637. total_appended_size = figW*w
  638. karray = self._determine_karray(equivalent_sizes, appended_sizes,
  639. max_equivalent_size,
  640. total_appended_size)
  641. ox = self._calc_offsets(appended_sizes, karray)
  642. ww = (ox[-1] - ox[0])/figW
  643. ref_h = equivalent_sizes[0]
  644. hh = (karray[0]*ref_h[0] + ref_h[1])/figH
  645. pb = mtransforms.Bbox.from_bounds(x, y, w, h)
  646. pb1 = mtransforms.Bbox.from_bounds(x, y, ww, hh)
  647. pb1_anchored = pb1.anchored(self.get_anchor(), pb)
  648. x0, y0 = pb1_anchored.x0, pb1_anchored.y0
  649. return x0, y0, ox, hh
  650. def locate(self, nx, ny, nx1=None, ny1=None, axes=None, renderer=None):
  651. """
  652. Parameters
  653. ----------
  654. axes_divider : AxesDivider
  655. nx, nx1 : int
  656. Integers specifying the column-position of the
  657. cell. When *nx1* is None, a single *nx*-th column is
  658. specified. Otherwise location of columns spanning between *nx*
  659. to *nx1* (but excluding *nx1*-th column) is specified.
  660. ny, ny1 : int
  661. Same as *nx* and *nx1*, but for row positions.
  662. axes
  663. renderer
  664. """
  665. figW, figH = self._fig.get_size_inches()
  666. x, y, w, h = self.get_position_runtime(axes, renderer)
  667. y_equivalent_sizes = self.get_vertical_sizes(renderer)
  668. x_appended_sizes = self.get_horizontal_sizes(renderer)
  669. x0, y0, ox, hh = self._locate(x, y, w, h,
  670. y_equivalent_sizes, x_appended_sizes,
  671. figW, figH)
  672. if nx1 is None:
  673. nx1 = nx+1
  674. x1, w1 = x0 + ox[nx]/figW, (ox[nx1] - ox[nx])/figW
  675. y1, h1 = y0, hh
  676. return mtransforms.Bbox.from_bounds(x1, y1, w1, h1)
  677. class VBoxDivider(HBoxDivider):
  678. """
  679. The Divider class whose rectangle area is specified as a subplot geometry.
  680. """
  681. def new_locator(self, ny, ny1=None):
  682. """
  683. returns a new locator
  684. (:class:`mpl_toolkits.axes_grid.axes_divider.AxesLocator`) for
  685. specified cell.
  686. Parameters
  687. ----------
  688. ny, ny1 : int
  689. Integers specifying the row-position of the
  690. cell. When *ny1* is None, a single *ny*-th row is
  691. specified. Otherwise location of rows spanning between *ny*
  692. to *ny1* (but excluding *ny1*-th row) is specified.
  693. """
  694. return AxesLocator(self, 0, ny, None, ny1)
  695. def locate(self, nx, ny, nx1=None, ny1=None, axes=None, renderer=None):
  696. """
  697. Parameters
  698. ----------
  699. axes_divider : AxesDivider
  700. nx, nx1 : int
  701. Integers specifying the column-position of the
  702. cell. When *nx1* is None, a single *nx*-th column is
  703. specified. Otherwise location of columns spanning between *nx*
  704. to *nx1* (but excluding *nx1*-th column) is specified.
  705. ny, ny1 : int
  706. Same as *nx* and *nx1*, but for row positions.
  707. axes
  708. renderer
  709. """
  710. figW, figH = self._fig.get_size_inches()
  711. x, y, w, h = self.get_position_runtime(axes, renderer)
  712. x_equivalent_sizes = self.get_horizontal_sizes(renderer)
  713. y_appended_sizes = self.get_vertical_sizes(renderer)
  714. y0, x0, oy, ww = self._locate(y, x, h, w,
  715. x_equivalent_sizes, y_appended_sizes,
  716. figH, figW)
  717. if ny1 is None:
  718. ny1 = ny+1
  719. x1, w1 = x0, ww
  720. y1, h1 = y0 + oy[ny]/figH, (oy[ny1] - oy[ny])/figH
  721. return mtransforms.Bbox.from_bounds(x1, y1, w1, h1)
  722. class LocatableAxesBase(object):
  723. def __init__(self, *kl, **kw):
  724. self._axes_class.__init__(self, *kl, **kw)
  725. self._locator = None
  726. self._locator_renderer = None
  727. def set_axes_locator(self, locator):
  728. self._locator = locator
  729. def get_axes_locator(self):
  730. return self._locator
  731. def apply_aspect(self, position=None):
  732. if self.get_axes_locator() is None:
  733. self._axes_class.apply_aspect(self, position)
  734. else:
  735. pos = self.get_axes_locator()(self, self._locator_renderer)
  736. self._axes_class.apply_aspect(self, position=pos)
  737. def draw(self, renderer=None, inframe=False):
  738. self._locator_renderer = renderer
  739. self._axes_class.draw(self, renderer, inframe)
  740. def _make_twin_axes(self, *kl, **kwargs):
  741. """
  742. Need to overload so that twinx/twiny will work with
  743. these axes.
  744. """
  745. if 'sharex' in kwargs and 'sharey' in kwargs:
  746. raise ValueError("Twinned Axes may share only one axis.")
  747. ax2 = type(self)(self.figure, self.get_position(True), *kl, **kwargs)
  748. ax2.set_axes_locator(self.get_axes_locator())
  749. self.figure.add_axes(ax2)
  750. self.set_adjustable('datalim')
  751. ax2.set_adjustable('datalim')
  752. self._twinned_axes.join(self, ax2)
  753. return ax2
  754. _locatableaxes_classes = {}
  755. def locatable_axes_factory(axes_class):
  756. new_class = _locatableaxes_classes.get(axes_class)
  757. if new_class is None:
  758. new_class = type(str("Locatable%s" % (axes_class.__name__)),
  759. (LocatableAxesBase, axes_class),
  760. {'_axes_class': axes_class})
  761. _locatableaxes_classes[axes_class] = new_class
  762. return new_class
  763. #if hasattr(maxes.Axes, "get_axes_locator"):
  764. # LocatableAxes = maxes.Axes
  765. #else:
  766. def make_axes_locatable(axes):
  767. if not hasattr(axes, "set_axes_locator"):
  768. new_class = locatable_axes_factory(type(axes))
  769. axes.__class__ = new_class
  770. divider = AxesDivider(axes)
  771. locator = divider.new_locator(nx=0, ny=0)
  772. axes.set_axes_locator(locator)
  773. return divider
  774. def make_axes_area_auto_adjustable(ax,
  775. use_axes=None, pad=0.1,
  776. adjust_dirs=None):
  777. if adjust_dirs is None:
  778. adjust_dirs = ["left", "right", "bottom", "top"]
  779. divider = make_axes_locatable(ax)
  780. if use_axes is None:
  781. use_axes = ax
  782. divider.add_auto_adjustable_area(use_axes=use_axes, pad=pad,
  783. adjust_dirs=adjust_dirs)
  784. #from matplotlib.axes import Axes
  785. from .mpl_axes import Axes
  786. LocatableAxes = locatable_axes_factory(Axes)