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